ブログを作成する

このページ

このガイドでは、EmDash でコンテンツ型の定義からカテゴリ・タグ付き投稿の表示まで、ブログを一から作る手順を説明します。

前提条件

  • EmDash サイトがセットアップ済みで動作していること(はじめにを参照)
  • Astro コンポーネントの基礎知識

投稿コレクションを定義する

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 には既定のカテゴリとタグのタクソノミーがあります。用語の作成と管理は タクソノミー を参照してください。

カテゴリで投稿を絞り込む

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

次のステップ