EmDash는 Astro 사이트에 관리 애플리케이션, 데이터베이스 기반 컬렉션, 미디어, 메뉴, 택소노미, 설정, 리비전, 미리보기를 추가합니다. 페이지와 컴포넌트는 일반 Astro 파일로 유지됩니다.
EmDash가 추가하는 것
| 기능 | 제공하는 것 |
|---|---|
| Admin | /_emdash/admin에서 브라우저 기반 컬렉션, 미디어, 메뉴, 택소노미, 설정 관리 |
| 데이터베이스 컬렉션 | 요청 시 조회되는 편집자 관리 콘텐츠 |
| 미디어 라이브러리 | 템플릿용 미디어 필드 값이 있는 저장된 이미지와 파일 |
| 초안, 리비전, 미리보기 | 게시 전 편집 작업 |
| 메뉴와 위젯 영역 | 항목 필드 외부의 정렬 가능하고 편집 가능한 사이트 영역 |
| 사이트 설정 | 제목, 태그라인, 로고, 페이지네이션 크기 등 공유 식별 및 표시 값 |
| 플러그인 | 훅, 라우트, 스토리지, 선택적 관리 확장 |
이러한 기능은 Astro를 대체하는 것이 아니라 Astro와 함께 존재합니다. Astro는 여전히 라우팅, 레이아웃, 렌더링, 스타일, 배포 어댑터를 제어합니다.
EmDash와 Astro 컬렉션
Astro 콘텐츠 컬렉션과 EmDash 컬렉션은 공존할 수 있습니다. 저장소 소유 콘텐츠에는 Astro 컬렉션을, /_emdash/admin을 통해 관리되는 콘텐츠에는 EmDash를 사용하세요.
| Astro 콘텐츠 컬렉션 | EmDash 컬렉션 | |
|---|---|---|
| 저장소 | 프로젝트 내 파일 | SQL 데이터베이스 |
| 편집 | 저장소 워크플로 | EmDash 관리자 |
| 쿼리 | getCollection() | getEmDashCollection() |
| 리치 텍스트 | Markdown 또는 MDX | Portable Text |
| 전달 | 빌드 시 또는 라이브 로더 | 런타임 라이브 로더 |
소유권이 다를 때 두 컬렉션 시스템을 모두 사용하세요. 예를 들어, 제품 사이트는 개발자가 작성한 릴리스 노트를 Astro 콘텐츠 컬렉션에, 편집자가 작성한 기사를 EmDash에 보관할 수 있습니다:
---
import { getCollection } from "astro:content";
import { getEmDashCollection } from "emdash";
const [releaseNotes, { entries: articles }] = await Promise.all([
getCollection("releases"),
getEmDashCollection("articles", { limit: 3 }),
]);
---
두 결과는 분리된 상태로 유지됩니다. EmDash는 파일 기반 항목을 데이터베이스에 복사하지 않습니다.
사이트 구성
현재 Node 템플릿은 Astro를 서버 출력으로 구성하고, EmDash 통합을 추가하며, SQLite 및 로컬 스토리지 어댑터를 사용합니다.
다음 간소화된 구성에는 필수 요소가 포함되어 있습니다:
import node from "@astrojs/node";
import react from "@astrojs/react";
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
output: "server",
adapter: node({ mode: "standalone" }),
integrations: [
react(),
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
}),
],
});
EmDash는 D1과 R2용으로 구성된 Cloudflare 템플릿도 제공합니다. Node 어댑터를 수동으로 변환하는 대신 배포 대상의 템플릿에서 시작하세요.
라이브 컬렉션 등록
템플릿은 _emdash라는 Astro 라이브 컬렉션을 통해 EmDash 콘텐츠를 노출합니다:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({ loader: emdashLoader() }),
};
getEmDashCollection()과 getEmDashEntry()는 이 로더를 통해 요청된 콘텐츠 유형을 선택합니다.
컬렉션 쿼리
다음 쿼리는 가장 최근에 게시된 게시물을 읽습니다. orderBy는 저장된 필드 이름을 사용하며 각 이름을 "asc" 또는 "desc"에 매핑합니다:
---
import { getEmDashCollection } from "emdash";
const { entries: posts, error, cacheHint } = await getEmDashCollection("posts", {
orderBy: { published_at: "desc" },
limit: 10,
});
if (error) return new Response("게시물을 불러올 수 없습니다", { status: 500 });
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---
{posts.map((post) => (
<article>
<h2><a href={`/posts/${post.id}`}>{post.data.title}</a></h2>
</article>
))}
익명 쿼리는 게시된 콘텐츠를 반환합니다. 명시적 status 필터는 인증된 코드나 미리보기 지원 코드에서 유용합니다. where는 콘텐츠 필드와 택소노미 이름을 받습니다. 전체 필터 및 페이지네이션 형태는 콘텐츠 쿼리를 참조하세요.
단일 항목 쿼리
슬러그 또는 데이터베이스 ID를 getEmDashEntry()에 전달합니다. 다음 라우트는 URL 슬러그를 사용합니다:
---
import { decodeSlug, getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.redirect("/404");
const { entry: post, error, cacheHint } = await getEmDashEntry("posts", slug);
if (error) return new Response("게시물을 불러올 수 없습니다", { status: 500 });
if (!post) return Astro.redirect("/404");
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
반환된 entry.id는 Astro의 라우트 식별자이며 일반적으로 슬러그입니다. 데이터베이스 콘텐츠 ID는 entry.data.id입니다. 저장된 콘텐츠 ID가 필요한 헬퍼에는 data.id를 사용하세요.
동적 CMS 기능 사용
EmDash는 단일 컬렉션 항목에 속하지 않는 데이터를 위한 서버 헬퍼를 내보냅니다:
---
import { getMenu, getSiteSettings } from "emdash";
import { WidgetArea } from "emdash/ui";
const [menu, settings] = await Promise.all([
getMenu("primary"),
getSiteSettings(),
]);
---
<header>
<a href="/">{settings.title}</a>
<nav>
{menu?.items.map((item) => <a href={item.url}>{item.label}</a>)}
</nav>
</header>
<main><slot /></main>
<aside><WidgetArea name="sidebar" /></aside>
플러그인 형식 선택
샌드박스 플러그인과 네이티브 플러그인은 패키지 구조가 다릅니다. 샌드박스 플러그인은 emdash-plugin.jsonc와 기본 내보내기된 src/plugin.ts 객체를 사용합니다. 네이티브 플러그인은 디스크립터 팩토리와 definePlugin()으로 구축된 createPlugin()을 내보냅니다.
플러그인을 추가하기 전에 플러그인 형식 선택을 읽으세요. 네이티브 definePlugin() 예제를 샌드박스 패키지에 복사하지 마세요.