EmDash는 Astro를 위해 특별히 구축된 CMS입니다. 데이터베이스 기반 콘텐츠, 세련된 관리 UI 및 WordPress 스타일 기능(메뉴, 위젯, 분류법)으로 Astro 사이트를 확장하면서 기대하는 개발자 경험을 유지합니다.
Astro에 대해 알고 있는 모든 것이 여전히 적용됩니다. EmDash는 기존 Astro 워크플로우 위에 콘텐츠 관리를 추가합니다.
EmDash가 추가하는 것
EmDash는 파일 기반 Astro 사이트에 부족한 콘텐츠 관리 기능을 제공합니다:
| 기능 | 설명 |
|---|---|
| 관리 UI | /_emdash/admin의 전체 WYSIWYG 편집 인터페이스 |
| 데이터베이스 저장소 | SQLite, libSQL, Cloudflare D1 또는 PostgreSQL에 저장된 콘텐츠 |
| 미디어 라이브러리 | 이미지 및 파일 업로드, 정리 및 제공 |
| 탐색 메뉴 | 중첩 기능이 있는 드래그 앤 드롭 메뉴 관리 |
| 위젯 영역 | 동적 사이드바 및 푸터 영역 |
| 사이트 설정 | 전역 구성(제목, 로고, 소셜 링크) |
| 분류법 | 카테고리, 태그 및 사용자 정의 분류법 |
| 미리보기 시스템 | 초안 콘텐츠용 서명된 미리보기 URL |
| 수정본 | 콘텐츠 버전 기록 |
Astro 컬렉션 vs EmDash
Astro의 astro:content 컬렉션은 파일 기반이며 빌드 시간에 확인됩니다. EmDash 컬렉션은 데이터베이스 기반이며 런타임에 확인됩니다.
| Astro 컬렉션 | EmDash 컬렉션 | |
|---|---|---|
| 저장소 | src/content/의 Markdown/MDX 파일 | SQL 데이터베이스(SQLite, libSQL, D1 또는 Postgres) |
| 편집 | 코드 에디터 | 관리 UI |
| 콘텐츠 형식 | 프론트매터가 있는 Markdown | Portable Text(구조화된 JSON) |
| 업데이트 | 재빌드 필요 | 즉시(SSR) |
| 스키마 | content.config.ts의 Zod | 관리자에서 정의되고 데이터베이스에 저장됨 |
| 최적 용도 | 개발자 관리 콘텐츠 | 편집자 관리 콘텐츠 |
함께 사용하기
Astro 컬렉션과 EmDash는 공존할 수 있습니다. 개발자 콘텐츠(문서, 변경 로그)에는 Astro 컬렉션을, 편집자 콘텐츠(블로그 게시물, 페이지)에는 EmDash를 사용하세요:
---
import { getCollection } from "astro:content";
import { getEmDashCollection } from "emdash";
// Developer-managed docs from files
const docs = await getCollection("docs");
// Editor-managed posts from database
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
limit: 5,
});
---
구성
EmDash에는 두 개의 구성 파일이 필요합니다.
Astro 통합
다음 구성은 서버 출력 모드에서 EmDash를 Astro 통합으로 등록합니다:
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
output: "server", // Required for EmDash
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
}),
],
});
라이브 컬렉션 로더
다음 파일은 EmDash를 라이브 콘텐츠 소스로 등록합니다:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
_emdash 컬렉션은 내부적으로 콘텐츠 유형(게시물, 페이지, 제품)으로 라우팅됩니다.
콘텐츠 쿼리
EmDash는 Astro의 라이브 콘텐츠 컬렉션 패턴을 따르는 쿼리 함수를 제공하며 { entries, error } 또는 { entry, error }를 반환합니다:
EmDash
import { getEmDashCollection, getEmDashEntry } from "emdash";
// Get all published posts - returns { entries, error }
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
});
// Get a single post by slug - returns { entry, error, isPreview }
const { entry: post } = await getEmDashEntry("posts", "my-post");
Astro
import { getCollection, getEntry } from "astro:content";
// Get all blog entries
const posts = await getCollection("blog");
// Get a single entry by slug
const post = await getEntry("blog", "my-post"); 필터링 옵션
getEmDashCollection은 Astro의 getCollection이 제공하지 않는 필터링을 지원합니다:
const { entries: posts } = await getEmDashCollection("posts", {
status: "published", // draft | published | archived
limit: 10, // max results
where: { category: "news" }, // taxonomy filter
});
콘텐츠 렌더링
EmDash는 구조화된 JSON 형식인 Portable Text로 리치 텍스트를 저장합니다. PortableText 컴포넌트로 렌더링하세요:
EmDash
---
import { getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
const { slug } = Astro.params;
const { entry: post } = await getEmDashEntry("posts", slug);
if (!post) {
return Astro.redirect("/404");
}
---
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article> Astro
---
import { getEntry, render } from "astro:content";
const { slug } = Astro.params;
const post = await getEntry("blog", slug);
const { Content } = await render(post);
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article> 동적 기능
EmDash는 Astro의 콘텐츠 레이어에 없는 WordPress 스타일 기능을 위한 API를 제공합니다.
탐색 메뉴
다음 레이아웃은 위치별로 메뉴를 가져와 중첩된 항목으로 렌더링합니다:
---
import { getMenu } from "emdash";
const primaryMenu = await getMenu("primary");
---
{primaryMenu && (
<nav>
<ul>
{primaryMenu.items.map(item => (
<li>
<a href={item.url}>{item.label}</a>
{item.children.length > 0 && (
<ul>
{item.children.map(child => (
<li><a href={child.url}>{child.label}</a></li>
))}
</ul>
)}
</li>
))}
</ul>
</nav>
)}
위젯 영역
다음 레이아웃은 위젯 영역을 가져와 각 위젯을 렌더링합니다:
---
import { getWidgetArea } from "emdash";
import { PortableText } from "emdash/ui";
const sidebar = await getWidgetArea("sidebar");
---
{sidebar && sidebar.widgets.length > 0 && (
<aside>
{sidebar.widgets.map(widget => (
<div class="widget">
{widget.title && <h3>{widget.title}</h3>}
{widget.type === "content" && widget.content && (
<PortableText value={widget.content} />
)}
</div>
))}
</aside>
)}
사이트 설정
다음 컴포넌트는 전역 사이트 설정을 읽고 로고 또는 제목을 렌더링합니다:
---
import { getSiteSettings, getSiteSetting } from "emdash";
const settings = await getSiteSettings();
// Or fetch individual values:
const title = await getSiteSetting("title");
---
<header>
{settings.logo ? (
<img src={settings.logo.url} alt={settings.title} />
) : (
<span>{settings.title}</span>
)}
{settings.tagline && <p>{settings.tagline}</p>}
</header>
플러그인
훅, 스토리지, 설정 및 관리 UI를 추가하는 플러그인으로 EmDash를 확장하세요:
import emdash from "emdash/astro";
import seoPlugin from "@emdash-cms/plugin-seo";
export default defineConfig({
integrations: [
emdash({
// ...
plugins: [seoPlugin({ generateSitemap: true })],
}),
],
});
definePlugin으로 사용자 정의 플러그인을 생성하세요:
import { definePlugin } from "emdash";
export default definePlugin({
id: "analytics",
version: "1.0.0",
capabilities: ["content:read"],
hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved", { id: event.content.id });
},
},
admin: {
settingsSchema: {
trackingId: { type: "string", label: "Tracking ID" },
},
},
});
서버 렌더링
EmDash 사이트는 SSR 모드로 실행되므로 콘텐츠가 런타임에 제공되고 변경 사항이 즉시 표시됩니다.
getStaticPaths를 사용하는 정적 페이지의 경우 콘텐츠는 빌드 시간에 가져옵니다:
---
import { getEmDashCollection, getEmDashEntry } from "emdash";
export async function getStaticPaths() {
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
});
return posts.map((post) => ({
params: { slug: post.data.slug },
}));
}
const { slug } = Astro.params;
const { entry: post } = await getEmDashEntry("posts", slug);
---
동적 페이지의 경우 prerender = false를 설정하여 각 요청에서 콘텐츠를 가져옵니다:
---
export const prerender = false;
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry: post, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("Server error", { status: 500 });
}
if (!post) {
return new Response(null, { status: 404 });
}
---
다음 단계
Getting Started
첫 번째 EmDash 사이트 만들기 5분 이내.
Querying Content
쿼리 API 배우기 상세히.
Create a Blog
완전한 블로그 구축 카테고리 및 태그 포함.
Deploy to Cloudflare
사이트를 프로덕션으로 배포 Workers에서.