このページで分かること

  • WordPressのテーマのファイルが、Astroのプロジェクトのどのディレクトリにあたるか
  • .astro コンポーネントの書き方(frontmatter、テンプレート式、props、スロット、レイアウト)とファイルベースのルーティング
  • EmDashのテンプレートがサーバーで描画する理由と、getEmDashCollection()getEmDashEntry() でコンテンツを取得する方法
難易度
基礎
読む時間
4分
前提知識
AstroとはCMSとは
このページの目次

EmDashのサイトでは、ページ、レイアウト、コンポーネント、サーバーでの描画をAstroが提供します。このガイドでは、現在のEmDashのテンプレートで使われているAstroの概念を説明します。WordPressのテーマとPHPのテンプレートをすでに理解していることを前提にしています。

EmDashに固有ではないフレームワークの機能については、Astroのドキュメントを使います。

プロジェクトの構成

Astroのサイトでは、ファイルの種類ごとに置くディレクトリが明示的に決まっています。現在のEmDashのテンプレートは、次の構成を使っています。

WordPress Astro 用途
index.phpsingle.phppage.php src/pages/ URLのルート
template-parts/ src/components/ 再利用するマークアップ
header.phpfooter.php src/layouts/ ページ共通の外枠
style.css src/styles/ サイトのスタイル
プラグインとデータベースの設定 astro.config.mjs インテグレーションとサーバーアダプター
テーマの初期設定データ seed/seed.json コレクション、メニュー、サンプルコンテンツ

ブログテンプレートは、公開URLと対応するルートのディレクトリを使っています。

Project structure
src/
├── components/
│   └── PostCard.astro
├── layouts/
│   └── Base.astro
├── pages/
│   ├── index.astro
│   ├── pages/
│   │   └── [slug].astro
│   └── posts/
│       ├── index.astro
│       └── [slug].astro
└── live.config.ts

Astroのコンポーネント

.astro コンポーネントは、サーバー側のTypeScriptとHTMLのテンプレートを組み合わせたものです。--- の区切り線で囲んだ部分のコードはサーバーで実行されます。2つ目の区切り線より下のマークアップが、レスポンスのHTMLになります。

次のコンポーネントは、フロントマターでpropsを宣言し、テンプレートでそれを描画します。

src/components/PostCard.astro
---
interface Props {
  title: string;
  excerpt?: string;
  href: string;
}

const { title, excerpt, href } = Astro.props;
---

<article>
  <h2><a href={href}>{title}</a></h2>
  {excerpt && <p>{excerpt}</p>}
</article>

Astroは、{value} で描画した値をエスケープします。import、データベースのクエリ、そのほかのサーバー側の処理は、フロントマターに書きます。

本サイトの補足 やさしい解説

WordPressのテーマでは、1つのPHPファイルの中でPHPのコードとHTMLを行き来します。Astroの .astro ファイルは、上の --- で囲んだ部分にサーバーで実行するコード(データの取得など)を書き、その下にHTMLを書く、という2段の構成です。{title} のように波かっこで囲むと値を表示でき、表示する値はAstroが自動でエスケープします。

テンプレートの式

PHPのテンプレートで <?php ?> に切り替える場面では、Astroのテンプレートでは波かっこを使います。EmDashのテンプレートでよく使う形は、値、条件、配列の展開です。

目的 Astroの構文
値を表示する {post.data.title}
値があるときだけ描画する {post.data.excerpt && <p>{post.data.excerpt}</p>}
2つの結果から選ぶ {posts.length === 0 ? <p>No posts yet.</p> : <PostList />}
一覧を描画する {posts.map((post) => <PostCard title={post.data.title} excerpt={post.data.excerpt} href={"/posts/" + post.id} />)}

式では、フロントマターで用意した変数、Astro.props の値、EmDashのクエリが返したデータを使えます。Astroは、既定で文字列の値をエスケープします。構造化されたリッチテキストには、HTMLを埋め込むのではなく、<PortableText /> などのレンダラーを使います。

propsとスロット

propsは、get_template_part() に渡す $args に相当します。propsを使うと、それぞれの入力が明示され、TypeScriptでチェックできます。

スロットを使うと、親からコンポーネントにマークアップを渡せます。デフォルトのスロットはページの中身に使い、名前付きのスロットは差し込む場所を追加するのに使います。

src/components/Card.astro
---
interface Props {
  title: string;
}

const { title } = Astro.props;
---

<article>
  <h2>{title}</h2>
  <slot />
  <footer><slot name="footer" /></footer>
</article>

次のページは、両方のスロットを埋めます。

src/pages/index.astro
---
import Card from "../components/Card.astro";
---

<Card title="Latest post">
  <p>The main card content.</p>
  <a slot="footer" href="/posts/latest">Read the post</a>
</Card>

スロットは、そのコンポーネントの呼び出しの中だけで有効です。別の場所で登録したコールバックを受け取れるWordPressのアクションとは、動きが異なります。

レイアウト

レイアウトは、WordPressのテーマではよく header.phpfooter.php に分けて書かれる、ページ共通の文書構造を受け持ちます。ページはレイアウトをimportし、自分の中身をレイアウトのスロットに渡します。

次のレイアウトは、文書の外枠を提供します。

src/layouts/Base.astro
---
interface Props {
  title: string;
}

const { title } = Astro.props;
---

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{title}</title>
  </head>
  <body>
    <header><a href="/">My site</a></header>
    <main><slot /></main>
  </body>
</html>

次のページは、レイアウトにタイトルとメインの中身を渡します。

src/pages/index.astro
---
import Base from "../layouts/Base.astro";
---

<Base title="Home">
  <h1>Latest posts</h1>
</Base>
本サイトの補足 やさしい解説

WordPressでは、各テンプレートの先頭で get_header()、末尾で get_footer() を呼んで共通部分を読み込みます。Astroでは、<html> から </html> までの共通の外枠を1つのレイアウトファイルにまとめます。各ページはそのレイアウトで自分の中身を囲み、中身はレイアウトの <slot /> の位置に入ります。ヘッダーを直したいときは、レイアウトファイルを1か所直せば全ページに反映されます。

ファイルベースのルーティング

src/pages/ の中のファイルがルートを決めます。角かっこで囲んだファイル名は、動的なセグメントになります。

ファイル URL
src/pages/index.astro /
src/pages/posts/index.astro /posts
src/pages/posts/[slug].astro /posts/hello-world
src/pages/pages/[slug].astro /pages/about

src/pages/posts/[slug].astro の中では、Astro.params.slug にURLの値が入ります。残余パラメーター、リダイレクト、そのほかのルーティングの機能については、Astroのルーティングを読みます。

サーバーでの描画

現在のEmDashのテンプレートは、astro.config.mjsoutput: "server" を使っています。そのため、ページはリクエストのたびにデータベースを取得でき、公開したコンテンツの反映は、新しい静的ビルドに依存しません。

サイトが意図的にEmDashをビルド時のデータソースとして扱う場合を除き、EmDashのテーマのルートに getStaticPaths() を追加しないでください。提供されているテーマは、サーバーで描画します。

フレームワークとしての動作については、Astroのオンデマンドレンダリングを読みます。

EmDashのコンテンツの取得

EmDashは、AstroのライブコンテンツコレクションgetEmDashCollection()getEmDashEntry() で包んでいます。コレクションの結果には、entries 配列が入ります。1件のエントリーの結果には entry が入り、一致する公開済みのエントリーがない場合は null になります。

次のアーカイブページでは、現在のブログテンプレートと同じ並び順と識別子を使っています。

src/pages/posts/index.astro
---
import { getEmDashCollection } from "emdash";
import Base from "../../layouts/Base.astro";

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

if (error) {
  return new Response("Could not load posts", { status: 500 });
}
---

<Base title="Posts">
  {posts.map((post) => (
    <article>
      <h2><a href={`/posts/${post.id}`}>{post.data.title}</a></h2>
      {post.data.excerpt && <p>{post.data.excerpt}</p>}
    </article>
  ))}
</Base>

post.id はAstroが公開するルートの識別子で、通常はエントリーのスラッグです。post.data.id はデータベース上の識別子です。タクソノミーやコメントのヘルパーのように、APIが保存されたコンテンツのIDを必要とする場合は、data.id を使います。

次の動的ルートは、URLのスラッグで投稿を探し、そのPortable Textのフィールドを描画します。

src/pages/posts/[slug].astro
---
import { decodeSlug, getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
import Base from "../../layouts/Base.astro";

const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.redirect("/404");

const { entry: post, error } = await getEmDashEntry("posts", slug);
if (error) return new Response("Could not load the post", { status: 500 });
if (!post) return Astro.redirect("/404");
---

<Base title={post.data.title}>
  <article>
    <h1>{post.data.title}</h1>
    <PortableText value={post.data.content} />
  </article>
</Base>
本サイトの補足 やさしい解説

WordPressの single.php にあたるのが src/pages/posts/[slug].astro です。URLの [slug] の部分(例:hello-world)を受け取り、getEmDashEntry() でその投稿を1件取得します。見つからなければ404ページに移動し、見つかればタイトルと本文を描画します。本文は the_content() の代わりに <PortableText /> で描画します。

Astroの学習を続ける

EmDashのテンプレートでは、コンポーネントのスタイルや、ブラウザーで動く小さなスクリプトも使っています。ただし、これらはEmDashの概念ではなく、Astroの通常の機能です。スコープ付きのスタイルとグローバルなスタイルについてはスタイルとCSSを、コンポーネントにブラウザー側の動作が必要な場合はスクリプトとイベントハンドリングを読みます。