블로그 만들기

이 페이지

이 가이드는 콘텐츠 유형 정의부터 카테고리·태그가 있는 글 표시까지, EmDash로 블로그를 처음부터 만드는 과정을 안내합니다.

사전 요구 사항

  • EmDash 사이트가 설정되어 실행 중일 것(시작하기 참고)
  • Astro 컴포넌트에 대한 기본 이해

글(posts) 컬렉션 정의

EmDash는 설정 시 기본 「posts」 컬렉션을 만듭니다. 사용자 지정은 관리 대시보드나 API로 하면 됩니다.

기본 posts 컬렉션 필드:

  • title — 글 제목
  • slug — URL에 쓰기 좋은 식별자
  • content — 리치 텍스트 본문
  • excerpt — 짧은 설명
  • featured_image — 헤더 이미지(선택)
  • status — 초안, 게시됨, 예약
  • publishedAt — 게시일(시스템 필드)

첫 글 작성하기

  1. 관리 대시보드 /_emdash/admin 열기

  2. 사이드바에서 Posts 클릭

    제목, 상태, 날짜가 보이는 EmDash 글 목록
  3. New Post 클릭

    제목, 본문, 게시 옵션이 있는 EmDash 글 편집기
  4. 제목을 입력하고 리치 텍스트 편집기로 본문 작성

  5. 사이드 패널에서 카테고리와 태그 추가

  6. 상태를 Published로 설정

  7. Save 클릭

글이 즉시 공개됩니다. 사이트를 다시 빌드할 필요가 없습니다.

사이트에 글 표시하기

모든 글 목록

게시된 모든 글을 보여 주는 페이지를 만듭니다.

---
import { getEmDashCollection } from "emdash";
import Base from "../../layouts/Base.astro";

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

// Sort by publication date, newest first
const sortedPosts = posts.sort(
  (a, b) => (b.data.publishedAt?.getTime() ?? 0) - (a.data.publishedAt?.getTime() ?? 0)
);
---

<Base title="Blog">
  <h1>Blog</h1>
  <ul>
    {sortedPosts.map((post) => (
      <li>
        <a href={`/blog/${post.data.slug}`}>
          <h2>{post.data.title}</h2>
          <p>{post.data.excerpt}</p>
          <time datetime={post.data.publishedAt?.toISOString()}>
            {post.data.publishedAt?.toLocaleDateString()}
          </time>
        </a>
      </li>
    ))}
  </ul>
</Base>

단일 글 표시

각 글에 대한 동적 경로를 만듭니다.

---
import { getEmDashCollection, getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
import Base from "../../layouts/Base.astro";

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

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

<Base title={post.data.title}>
  <article>
    {post.data.featured_image && (
      <img src={post.data.featured_image} alt="" />
    )}
    <h1>{post.data.title}</h1>
    <time datetime={post.data.publishedAt?.toISOString()}>
      {post.data.publishedAt?.toLocaleDateString()}
    </time>
    <PortableText value={post.data.content} />
  </article>
</Base>

카테고리와 태그 추가

EmDash에는 기본 카테고리·태그 분류가 있습니다. 용어 생성·관리는 분류(taxonomies)를 참고하세요.

카테고리로 글 필터링

---
import { getEmDashCollection, getTerm, getTaxonomyTerms } from "emdash";
import Base from "../../layouts/Base.astro";

export async function getStaticPaths() {
  const categories = await getTaxonomyTerms("category");

  // Flatten hierarchical categories
  const flatten = (terms) => terms.flatMap((t) => [t, ...flatten(t.children)]);

  return flatten(categories).map((cat) => ({
    params: { slug: cat.slug },
    props: { category: cat },
  }));
}

const { category } = Astro.props;

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

<Base title={category.label}>
  <h1>{category.label}</h1>
  {category.description && <p>{category.description}</p>}

  <ul>
    {posts.map((post) => (
      <li>
        <a href={`/blog/${post.data.slug}`}>{post.data.title}</a>
      </li>
    ))}
  </ul>
</Base>

글에 카테고리 표시

개별 글에 카테고리를 표시합니다.

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

interface Props {
  postId: string;
}

const { postId } = Astro.props;
const categories = await getEntryTerms("posts", postId, "category");
const tags = await getEntryTerms("posts", postId, "tag");
---

<div class="post-meta">
  {categories.length > 0 && (
    <div class="categories">
      <span>Categories:</span>
      {categories.map((cat) => (
        <a href={`/category/${cat.slug}`}>{cat.label}</a>
      ))}
    </div>
  )}

  {tags.length > 0 && (
    <div class="tags">
      <span>Tags:</span>
      {tags.map((tag) => (
        <a href={`/tag/${tag.slug}`}>{tag.label}</a>
      ))}
    </div>
  )}
</div>

페이지네이션 추가

글이 많을 때 페이지를 나눕니다.

---
import { getEmDashCollection } from "emdash";
import Base from "../../../layouts/Base.astro";

const POSTS_PER_PAGE = 10;

export async function getStaticPaths() {
  const { entries: allPosts } = await getEmDashCollection("posts", {
    status: "published",
  });

  const totalPages = Math.ceil(allPosts.length / POSTS_PER_PAGE);

  return Array.from({ length: totalPages }, (_, i) => ({
    params: { page: String(i + 1) },
    props: { currentPage: i + 1, totalPages },
  }));
}

const { currentPage, totalPages } = Astro.props;

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

const sortedPosts = allPosts.sort(
  (a, b) => (b.data.publishedAt?.getTime() ?? 0) - (a.data.publishedAt?.getTime() ?? 0)
);

const start = (currentPage - 1) * POSTS_PER_PAGE;
const posts = sortedPosts.slice(start, start + POSTS_PER_PAGE);
---

<Base title={`Blog - Page ${currentPage}`}>
  <h1>Blog</h1>

  <ul>
    {posts.map((post) => (
      <li>
        <a href={`/blog/${post.data.slug}`}>{post.data.title}</a>
      </li>
    ))}
  </ul>

  <nav>
    {currentPage > 1 && (
      <a href={`/blog/page/${currentPage - 1}`}>Previous</a>
    )}
    <span>Page {currentPage} of {totalPages}</span>
    {currentPage < totalPages && (
      <a href={`/blog/page/${currentPage + 1}`}>Next</a>
    )}
  </nav>
</Base>

RSS 피드 추가

블로그용 RSS 피드를 만듭니다.

import rss from "@astrojs/rss";
import { getEmDashCollection } from "emdash";

export async function GET(context) {
	const { entries: posts } = await getEmDashCollection("posts", {
		status: "published",
	});

	return rss({
		title: "My Blog",
		description: "A blog built with EmDash",
		site: context.site,
		items: posts.map((post) => ({
			title: post.data.title,
			pubDate: post.data.publishedAt,
			description: post.data.excerpt,
			link: `/blog/${post.data.slug}`,
		})),
	});
}

RSS 패키지가 없다면 설치합니다.

npm install @astrojs/rss

다음 단계