コンテンツにスキップ

ブログを作成する

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

  • EmDashサイトのセットアップと稼働(はじめにを参照)
  • Astroコンポーネントの基本的な理解

EmDashはセットアップ時にデフォルトの「posts」コレクションを作成します。カスタマイズが必要な場合は、管理ダッシュボードまたはAPIを使用してください。

デフォルトの投稿コレクションには以下が含まれます:

  • title - 投稿タイトル
  • slug - URLフレンドリーな識別子
  • content - リッチテキスト本文
  • excerpt - 短い説明
  • featured_image - ヘッダー画像(オプション)
  • status - 下書き、公開、または予約済み
  • publishedAt - 公開日(システムフィールド)
  1. 管理ダッシュボード /_emdash/admin を開く

  2. サイドバーの Posts をクリック

    EmDash posts list showing titles, status, and dates
  3. New Post をクリック

    EmDash post editor with title, content, and publish options
  4. タイトルを入力し、リッチテキストエディタでコンテンツを記述

  5. サイドバーパネルでカテゴリとタグを追加

  6. ステータスを Published に設定

  7. Save をクリック

投稿が公開されました。再ビルドは不要です。

公開済み投稿をすべて表示するページを作成:

src/pages/blog/index.astro
---
import { getEmDashCollection } from "emdash";
import Base from "../../../layouts/Base.astro";
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
});
// 公開日でソート(新しい順)
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>

個別投稿用の動的ルートを作成:

astro title="src/pages/blog/[slug].astro"
---
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には組み込みのカテゴリとタグ分類が含まれています。用語の作成と管理の詳細については分類を参照してください。

カテゴリで投稿をフィルタリング

Section titled “カテゴリで投稿をフィルタリング”
astro title="src/pages/category/[slug].astro"
---
import { getEmDashCollection, getTerm, getTaxonomyTerms } from "emdash";
import Base from "../../../layouts/Base.astro";
export async function getStaticPaths() {
const categories = await getTaxonomyTerms("category");
// 階層カテゴリを平坦化
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>

個別投稿にカテゴリを表示:

astro title="src/components/PostMeta.astro"
---
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>

投稿数が多いブログにはページネーションを追加:

astro title="src/pages/blog/page/[page].astro"
---
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フィードを作成:

ts title="src/pages/rss.xml.ts"
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: "私のブログ",
description: "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パッケージがインストールされていない場合はインストール:

Terminal window
npm install @astrojs/rss