ブログを作る
このページで分かること
- Cloudflare版のブログテンプレートからサイトの雛形を作り、ローカルで動かす手順(Node.js版は
--template node:blogを指定) - テンプレートのコンテンツモデル(
posts・pages、カテゴリー・タグ、バイライン)と、管理画面で最初の記事を公開するまでの操作 - 記事一覧・記事ページ・カテゴリーとタグのアーカイブ・RSSフィードを描画するテンプレートのコードの流れ
このページの目次
EmDashのブログテンプレートは、投稿、固定ページ、執筆者、カテゴリー、タグ、検索、コメント、ウィジェット、RSSフィードを備えた、動作するAstroのサイトを提供します。このチュートリアルでは、Cloudflare版を作成し、投稿を1件公開して、その投稿がテンプレートのコードの中でどう扱われるかをたどります。
前提条件
始める前に、Node.js 22.12以上とpnpmをインストールします。
Cloudflareのアカウントが必要になるのは、サイトをデプロイするときだけです。ローカルでの開発中は、テンプレートがデータベースとファイルストレージのローカル版を自分のパソコン上で動かします。
ブログの雛形を作る
次のコマンドは、Cloudflareのブログテンプレートから my-blog を作成し、pnpmで依存パッケージをインストールします。
npm create emdash@latest my-blog -- --template cloudflare:blog --pm pnpm --yes
作成ツールは、EMDASH_ENCRYPTION_KEY を含むローカルの .env ファイルも作成します。生成される .gitignore は、.env をバージョン管理から除外します。依存パッケージのインストールに失敗したと表示された場合は、プロジェクトのディレクトリに移動し、pnpm install を実行してから先に進みます。
ローカルの開発サーバーを起動します。
cd my-blog
pnpm dev
ターミナルに表示されたローカルのURLを開き、続けて /_emdash/admin を開きます。初回の起動であれば、セットアップ画面を完了します。セットアップの中で、テンプレートのシードデータがコンテンツモデルとサンプルコンテンツを作成します。
コンテンツモデルの理解
テンプレートは、seed/seed.json で2つのコレクションを定義しています。
postsは、supportsで下書き、リビジョン、検索、SEOを有効にし、それとは別にcommentsEnabled: trueでコメントを有効にしています。pagesは、下書き、リビジョン、検索に対応しています。
各投稿は、次の独自のフィールドを持っています。
| フィールド | 用途 |
|---|---|
title |
必須の投稿タイトル |
featured_image |
投稿と一緒に表示する画像(任意) |
content |
Portable Textの本文 |
excerpt |
投稿の一覧や、メタデータの代わりに使う短いテキスト |
EmDashは、変わらないコンテンツのID、スラッグ、状態、作成日時と更新日時、公開日時などのシステムフィールドを追加します。テンプレートはさらに、投稿用の category と tag のタクソノミーと、1人以上の執筆者をクレジットできるバイラインを定義しています。
開発サーバーは、このスキーマから emdash-env.d.ts を生成します。その結果、getEmDashCollection("posts") が返すエントリーの data プロパティには、Post の型が付きます。
やさしい解説
WordPressでいうと、posts は「投稿」、pages は「固定ページ」にあたる投稿タイプ(EmDashではコレクション)です。title や excerpt などのフィールドは、投稿メタにあたります。IDやスラッグ、公開日時のように、どのコレクションにも必要な項目は、EmDashが自動で追加します。カテゴリーとタグは、WordPressと同じくタクソノミーとして用意されています。
最初の投稿の公開
-
管理画面のサイドバーで「Posts」を選択し、「新規追加」を選択します。
-
タイトルを入力します。EmDashがタイトルからスラッグを提案します。公開URLを別の値にしたい場合は編集します。
-
抜粋を追加し、「Content」エディターで本文を書きます。
-
メディアライブラリからアイキャッチ画像を選択するか、アップロードします。投稿の中でのその画像の役割を説明する代替テキストを追加します。
-
設定パネルで、バイライン、カテゴリー、関連するタグを割り当てます。
-
「保存」を選択します。エントリーが下書きになり、エディターがそのエントリーの固定のURLで開き直します。
-
「プレビュー」を選択し、投稿のページを確認します。下書きの準備ができたら、エディターに戻って「公開」を選択します。
ローカルのサイトで /posts/your-post-slug を開きます。投稿は、トップページと投稿のアーカイブページにも表示されます。表示されない場合は、エディターに「下書き」や「予約済み」ではなく、「公開済み」と表示されていることを確認します。
公開したあとの編集は、公開中の投稿をそのままにして、新しい下書きに自動保存されます。修正した下書きで公開中の版を置き換えるときは、「Publish changes」を選択します。プレビュー、予約公開、リビジョン、編集ロックについては、コンテンツの作成ガイドで説明しています。
やさしい解説
WordPressと同じく、「保存」だけでは下書きのままで、「公開」を選択して初めてサイトに表示されます。公開したあとに記事を直すと、その変更は新しい下書きとして保存され、公開中の記事はそのまま表示され続けます。直した内容をサイトに出すには「Publish changes」を選択します。なお、ローカルで作った投稿は本番環境に自動でコピーされないため、デプロイ後は本番の管理画面で改めて作成します。
コレクションのクエリをたどる
トップページと投稿のアーカイブページは、サーバーで描画するときに getEmDashCollection() を呼び出します。テンプレートは、保存されている published_at フィールドで、データベースの中の投稿を並べ替えます。
---
import { getEmDashCollection, getTermsForEntries } from "emdash";
const { entries: posts, cacheHint } = await getEmDashCollection("posts", {
orderBy: { published_at: "desc" },
});
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
const tagsByEntry = await getTermsForEntries(
"posts",
posts.map((post) => post.data.id),
"tag",
);
---
コレクションのクエリは、既定で公開済みのエントリーを返します。並べ替えには、データベースのフィールド名である published_at を使います。返される publishedAt プロパティは、描画に使うJavaScriptの Date です。
タクソノミーの割り当ては変わらないコンテンツのIDに結び付いているため、タクソノミーのヘルパーには post.data.id を渡します。一方、リンクには post.id を使います。これは、コンテンツローダーが作る、URLに使うスラッグだからです。
<a href={`/posts/${post.id}`}>
<h2>{post.data.title}</h2>
{post.data.excerpt && <p>{post.data.excerpt}</p>}
</a>
現在のテンプレートは、投稿ごとに1回ずつ取得するのではなく、getTermsForEntries() でタグの取得をまとめています。バイラインは、コレクションのクエリによって、すでに post.data.bylines に含まれています。
投稿のクエリをたどる
投稿の動的ルートは、URLからスラッグを読み取り、getEmDashEntry() を呼び出します。次の抜粋は、クエリと描画の要点だけを示しています。完全なテンプレートでは、SEO、バイライン、コメント、関連する投稿、ウィジェットも扱っています。
---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Image, PortableText } from "emdash/ui";
const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.redirect("/404");
const { entry: post, error, cacheHint } = await getEmDashEntry("posts", slug);
if (error) return new Response("Unable to load post", { status: 500 });
if (!post) return Astro.redirect("/404");
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---
<article>
{post.data.featured_image && <Image image={post.data.featured_image} priority />}
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
Image は、編集者が選択したメディアの値を読み込み、レスポンシブな出力を生成します。PortableText は、保存されたブロックのデータを、見出し、段落、リンク、画像、コードブロック、そのほかの対応しているブロックの種類に変換します。
どちらのブログテンプレートも、astro.config.mjs で output: "server" を設定しています。これらのクエリはリクエストを描画するときに実行されるため、公開したコンテンツの表示は、ビルド時に作られる静的なルートの一覧に依存しません。
やさしい解説
WordPressの single.php にあたるのが、この src/pages/posts/[slug].astro です。URLの末尾(スラッグ)を読み取り、getEmDashEntry() でその投稿を1件取得します。投稿がなければ404ページに移動し、あればアイキャッチ画像、タイトル、本文を描画します。本文は the_content() の代わりに <PortableText /> で描画します。
カテゴリーとタグの利用
テンプレートには、カテゴリーごと・タグごとのアーカイブのルートがあります。カテゴリーのルートでは、まずタームのスラッグを解決し、そのタクソノミーで投稿を絞り込みます。
---
import { decodeSlug, getEmDashCollection, getTerm } from "emdash";
const slug = decodeSlug(Astro.params.slug);
const category = slug
? await getTerm("category", slug, { includeCounts: false })
: null;
if (!category) return Astro.redirect("/404");
const { entries: posts, error } = await getEmDashCollection("posts", {
where: { category: category.slug },
orderBy: { published_at: "desc" },
});
if (error) return new Response("Unable to load posts", { status: 500 });
---
tag のルートも、getTerm("tag", slug) と where: { tag: term.slug } を使った同じ形です。タームとその割り当ては、編集者が管理画面で管理します。階層のあるカテゴリー、階層のないタグ、独自のタクソノミーについては、タクソノミーガイドで説明しています。
getEmDashEntry() の結果には、投稿に割り当てられたタームが含まれます。そのため、詳細ページのルートでは、別のクエリを使わずにタームを描画できます。
---
const categories = post.data.terms?.category ?? [];
const tags = post.data.terms?.tag ?? [];
---
{categories.map((category) => (
<a href={`/category/${category.slug}`}>{category.label}</a>
))}
{tags.map((tag) => (
<a href={`/tag/${tag.slug}`}>{tag.label}</a>
))}
アーカイブのページ分割の追加
テンプレートの投稿のアーカイブページは、公開済みの投稿をすべて描画します。アーカイブの件数が増えたら、limit を追加し、/posts/page/2 のような番号付きのルートにはオフセットによるページ分割を、「Older posts」のリンクにはカーソルによるページ分割を使います。エントリーの並び順が予期せず変わらないよう、ページ間で orderBy: { published_at: "desc" } を変えないようにします。
ページ分割の例で、両方の方法と、それぞれを選ぶ場面を説明しています。
RSSフィードの確認
テンプレートは、最初から /rss.xml を配信しています。このエンドポイントは、getEmDashCollection() で新しい順に20件の投稿を読み込み、それぞれの公開日を整形し、タイトルと抜粋をエスケープしてからXMLに挿入します。また、EmDashの設定からサイトのタイトルとキャッチフレーズを読み込みます。
テスト用の投稿を公開したあと、/rss.xml を開いて、その投稿のタイトルを検索します。フィードで本番環境の絶対URLを使う場合は、デプロイする前にAstroの site オプションを設定します。ローカルでの開発中は、エンドポイントは現在のリクエストのオリジンを代わりに使います。
ここまでで、ブログには、執筆のワークフロー、実行時に描画される投稿のページ、タクソノミーのアーカイブ、メディアの描画、フィードがそろいました。絞り込みとページ分割についてはコンテンツの取得を、アセットの編集と使用箇所の追跡についてはメディアライブラリを読み進めます。AIアシスタントで投稿を下書き・編集するには、AIツールに従います。