EmDash는 Astro 페이지와 컴포넌트에서 콘텐츠를 가져오기 위한 쿼리 함수를 제공합니다. 이 함수들은 Astro의 라이브 콘텐츠 컬렉션 패턴을 따르며, 오류 처리가 포함된 구조화된 결과를 반환합니다.
쿼리 함수
| 함수 | 용도 | 반환값 |
|---|---|---|
getEmDashCollection | 콘텐츠 유형의 모든 항목 가져오기 | { entries, error } |
getEmDashEntry | ID 또는 슬러그로 단일 항목 가져오기 | { entry, error, isPreview } |
import { getEmDashCollection, getEmDashEntry } from "emdash";
모든 항목 가져오기
---
import { getEmDashCollection } from "emdash";
const { entries: posts, error } = await getEmDashCollection("posts");
if (error) {
console.error("게시물 로드 실패:", error);
}
---
<ul>
{posts.map((post) => (
<li>{post.data.title}</li>
))}
</ul>
로케일로 필터링
i18n이 활성화되면, 로케일로 필터링하여 특정 언어의 콘텐츠를 가져옵니다:
const { entries: frenchPosts } = await getEmDashCollection("posts", {
locale: "fr",
status: "published",
});
const { entries: localizedPosts } = await getEmDashCollection("posts", {
locale: Astro.currentLocale,
status: "published",
});
단일 항목의 경우 locale을 세 번째 인수로 전달합니다:
const { entry: post } = await getEmDashEntry("posts", "my-post", {
locale: Astro.currentLocale,
});
locale을 생략하면 요청의 현재 로케일이 기본값으로 사용됩니다. 요청된 로케일에 대한 번역이 없으면 폴백 체인을 따릅니다.
상태로 필터링
const { entries: published } = await getEmDashCollection("posts", {
status: "published",
});
const { entries: drafts } = await getEmDashCollection("posts", {
status: "draft",
});
결과 제한
const { entries: recentPosts } = await getEmDashCollection("posts", {
status: "published",
limit: 5,
});
택소노미로 필터링
const { entries: newsPosts } = await getEmDashCollection("posts", {
status: "published",
where: { category: "news" },
});
const { entries: jsPosts } = await getEmDashCollection("posts", {
status: "published",
where: { tag: "javascript" },
});
const { entries: featuredNews } = await getEmDashCollection("posts", {
status: "published",
where: { category: ["news", "featured"] },
});
where 필터는 단일 택소노미에 여러 값이 제공될 때 OR 로직을 사용합니다.
오류 처리
const { entries: posts, error } = await getEmDashCollection("posts");
if (error) {
console.error("게시물 로드 실패:", error);
return new Response("서버 오류", { status: 500 });
}
단일 항목 가져오기
---
import { getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
const { slug } = Astro.params;
const { entry: post, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("서버 오류", { status: 500 });
}
if (!post) {
return Astro.redirect("/404");
}
---
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
항목 반환 타입
interface EntryResult<T> {
entry: ContentEntry<T> | null;
error?: Error;
isPreview: boolean;
}
interface ContentEntry<T> {
id: string;
data: T;
edit: EditProxy;
}
SEO 패널 데이터 렌더링
supports: ["seo"]가 있는 컬렉션에서 편집자는 관리자의 SEO 패널에서 SEO 제목, 메타 설명, OG 이미지, 정규 URL, “검색 엔진에서 숨기기” (noindex) 토글을 설정할 수 있습니다. 해당 데이터는 entry.data.seo로 전달됩니다. getSeoMeta를 사용하여 패널 필드를 렌더링 가능한 메타 태그로 변환하세요:
---
import { getEmDashEntry, getSeoMeta } from "emdash";
const { entry, error } = await getEmDashEntry("posts", Astro.params.slug);
if (error) {
return new Response("서버 오류", { status: 500 });
}
if (!entry) return Astro.redirect("/404");
const seo = getSeoMeta(entry, {
siteTitle: "내 사이트",
siteUrl: "https://example.com",
path: Astro.url.pathname,
});
---
<head>
<title>{seo.title}</title>
{seo.description && <meta name="description" content={seo.description} />}
{seo.ogImage && <meta property="og:image" content={seo.ogImage} />}
{seo.canonical && <link rel="canonical" href={seo.canonical} />}
{seo.robots && <meta name="robots" content={seo.robots} />}
</head>
미리보기 모드
EmDash는 미들웨어를 통해 자동으로 미리보기를 처리합니다:
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("서버 오류", { status: 500 });
}
if (!entry) {
return Astro.redirect("/404");
}
---
{isPreview && (
<div class="preview-banner">
미리보기 표시 중. 이 콘텐츠는 게시되지 않았습니다.
</div>
)}
<article>
<h1>{entry.data.title}</h1>
<PortableText value={entry.data.content} />
</article>
비주얼 편집
<article {...entry.edit}>
<h1 {...entry.edit.title}>{entry.data.title}</h1>
<div {...entry.edit.content}>
<PortableText value={entry.data.content} />
</div>
</article>
편집 모드에서 {...entry.edit.title}은 data-emdash-ref 속성을 생성합니다. 프로덕션에서는 출력을 생성하지 않습니다.
인라인 코드 블록 스타일링
| 속성 | 용도 |
|---|---|
--emdash-inline-code-background | 코드 블록 배경 |
--emdash-inline-code-foreground | 일반 코드 텍스트 |
--emdash-inline-code-muted | 주석 및 인용 텍스트 |
--emdash-inline-code-keyword | 키워드, 리터럴, 셀렉터, 삭제된 텍스트 |
--emdash-inline-code-string | 문자열, 속성, 심볼, 추가된 텍스트 |
--emdash-inline-code-number | 숫자 및 메타데이터 |
--emdash-inline-code-title | 제목, 이름, 타입, 빌트인 |
--emdash-inline-code-border | 언어 셀렉터 테두리 |
--emdash-inline-code-control-background | 언어 셀렉터 배경 |
--emdash-inline-code-control-foreground | 언어 셀렉터 텍스트 및 아이콘 |
--emdash-inline-code-focus | 키보드 포커스 인디케이터 |
:root {
--emdash-inline-code-background: #f7f7f5;
--emdash-inline-code-foreground: #24292f;
--emdash-inline-code-muted: #57606a;
--emdash-inline-code-keyword: #b8172a;
--emdash-inline-code-string: #0a3069;
--emdash-inline-code-number: #0550ae;
--emdash-inline-code-title: #7545c7;
--emdash-inline-code-border: #7d8590;
--emdash-inline-code-control-background: #fff;
--emdash-inline-code-control-foreground: #24292f;
--emdash-inline-code-focus: #0550ae;
}
:root.dark {
--emdash-inline-code-background: #202020;
--emdash-inline-code-foreground: #f0f3f6;
--emdash-inline-code-muted: #c9d1d9;
--emdash-inline-code-keyword: #ffc1bb;
--emdash-inline-code-string: #b9ddff;
--emdash-inline-code-number: #a8d5ff;
--emdash-inline-code-title: #e5ccff;
--emdash-inline-code-border: #6e7681;
--emdash-inline-code-control-background: #161b22;
--emdash-inline-code-control-foreground: #f0f3f6;
--emdash-inline-code-focus: #a8d5ff;
}
결과 정렬
const { entries: posts } = await getEmDashCollection("posts", { status: "published" });
const sorted = posts.sort(
(a, b) => (b.data.publishedAt?.getTime() ?? 0) - (a.data.publishedAt?.getTime() ?? 0),
);
일반적인 정렬 패턴
posts.sort((a, b) => a.data.title.localeCompare(b.data.title));
posts.sort((a, b) => (a.data.order ?? 0) - (b.data.order ?? 0));
posts.sort(() => Math.random() - 0.5);
TypeScript 타입
npx emdash types
import { getEmDashCollection, getEmDashEntry } from "emdash";
import type { Post } from "../.emdash/types";
const { entries: posts } = await getEmDashCollection<Post>("posts");
const { entry: post } = await getEmDashEntry<Post>("posts", "my-post");
정적 vs. 서버 렌더링
정적 (사전 렌더링)
---
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);
---
서버 렌더링
---
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("서버 오류", { status: 500 });
if (!post) return new Response(null, { status: 404 });
---
성능 고려사항
캐싱
---
const { entries: posts } = await getEmDashCollection("posts", { status: "published" });
Astro.response.headers.set("Cache-Control", "public, max-age=300");
---
중복 쿼리 방지
---
import { getEmDashCollection } from "emdash";
import PostList from "../components/PostList.astro";
import Sidebar from "../components/Sidebar.astro";
const { entries: posts } = await getEmDashCollection("posts", { status: "published" });
const featured = posts.filter((p) => p.data.featured);
const recent = posts.slice(0, 5);
---
<PostList posts={featured} />
<Sidebar posts={recent} />