콘텐츠 조회

이 페이지

EmDash는 Astro 페이지와 컴포넌트에서 콘텐츠를 가져오는 쿼리 함수를 제공합니다. Astro의 live content collections 패턴을 따르며 구조화된 결과와 오류 처리를 제공합니다.

쿼리 함수

EmDash는 두 가지 주요 쿼리 함수를 제공합니다:

함수용도반환
getEmDashCollection콘텐츠 타입의 모든 항목 조회{ entries, error }
getEmDashEntryID 또는 slug로 한 항목 조회{ entry, error, isPreview }

emdash에서 가져옵니다:

import { getEmDashCollection, getEmDashEntry } from "emdash";

전체 항목 가져오기

getEmDashCollection으로 콘텐츠 타입의 모든 항목을 가져옵니다:

---
import { getEmDashCollection } from "emdash";

const { entries: posts, error } = await getEmDashCollection("posts");

if (error) {
  console.error("Failed to load posts:", 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",
});

결과 제한

반환되는 항목 수를 제한합니다:

// 최근 게시물 5개
const { entries: recentPosts } = await getEmDashCollection("posts", {
	status: "published",
	limit: 5,
});

분류법으로 필터

카테고리, 태그 또는 사용자 정의 분류 용어로 항목을 필터링합니다:

// "news" 카테고리의 게시물
const { entries: newsPosts } = await getEmDashCollection("posts", {
	status: "published",
	where: { category: "news" },
});

// "javascript" 태그가 있는 게시물
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("Failed to load posts:", error);
	return new Response("Server error", { status: 500 });
}

단일 항목 가져오기

getEmDashEntry로 ID 또는 slug로 한 항목을 가져옵니다:

---
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("Server error", { status: 500 });
}

if (!post) {
  return Astro.redirect("/404");
}
---

<article>
  <h1>{post.data.title}</h1>
  <PortableText value={post.data.content} />
</article>

항목 반환 타입

getEmDashEntry는 결과 객체를 반환합니다:

interface EntryResult<T> {
	entry: ContentEntry<T> | null; // 없으면 null
	error?: Error; // 실제 오류인 경우에만 설정("찾을 수 없음"은 아님)
	isPreview: boolean; // 미리보기/초안 콘텐츠를 보고 있으면 true
}

interface ContentEntry<T> {
	id: string;
	data: T;
	edit: EditProxy; // 시각적 편집 주석
}

entry 안의 data 객체는 콘텐츠 타입에 정의된 모든 필드를 포함합니다. edit 프록시는 시각적 편집 주석을 제공합니다(아래 참조).

미리보기 모드

EmDash는 미들웨어로 미리보기를 자동 처리합니다. URL에 유효한 _preview 토큰이 있으면 미들웨어가 이를 검증하고 요청 컨텍스트를 설정합니다. 쿼리 함수는 특별한 매개변수 없이 초안 콘텐츠를 제공합니다:

---
import { getEmDashEntry } from "emdash";

const { slug } = Astro.params;

// 특별한 미리보기 처리 불필요 — 미들웨어가 자동 처리
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);

if (error) {
  return new Response("Server error", { 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>

시각적 편집

쿼리 함수가 반환하는 모든 항목에는 템플릿에 주석을 다는 edit 프록시가 포함됩니다. 요소에 스프레드하면 인증된 편집자를 위한 인라인 편집이 활성화됩니다:

<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 속성을 생성합니다. 프로덕션에서 프록시 스프레드는 아무 출력도 생성하지 않습니다 — 런타임 비용 제로.

결과 정렬

getEmDashCollection은 정렬 순서를 보장하지 않습니다. 템플릿에서 결과를 정렬합니다:

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 타입

컬렉션에 대한 TypeScript 타입을 생성합니다:

npx emdash types

각 컬렉션의 인터페이스가 .emdash/types.ts에 생성됩니다. 타입 안전을 위해 사용합니다:

import { getEmDashCollection, getEmDashEntry } from "emdash";
import type { Post } from "../.emdash/types";

// 타입 안전 컬렉션 쿼리
const { entries: posts } = await getEmDashCollection<Post>("posts");
// posts는 ContentEntry<Post>[]

// 타입 안전 항목 쿼리
const { entry: post } = await getEmDashEntry<Post>("posts", "my-post");
// post는 ContentEntry<Post> | null

정적 vs. 서버 렌더링

EmDash 콘텐츠는 정적 페이지와 서버 렌더링 페이지 모두에서 동작합니다.

정적 (사전 렌더링)

정적 페이지의 경우 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);
---

서버 렌더링

서버 렌더링 페이지의 경우 콘텐츠를 직접 조회합니다:

---
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 });
}
---

성능 고려사항

캐싱

EmDash는 캐싱을 자동으로 처리하는 Astro의 live content collections를 사용합니다. 서버 렌더링 페이지의 경우 HTTP 캐시 헤더 추가를 고려합니다:

---
const { entries: posts } = await getEmDashCollection("posts", {
  status: "published",
});

// 5분 캐시
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} />

다음 단계