Astroで作るブログの仕組み
Astroでつくるブログの仕組み解説
はじめに
こんにちは、坂爪です。この記事ではAstroを使ったブログシステムの基本的な仕組みについて解説します。Astroの特徴からディレクトリ構成、そして実際の実装方法まで、順を追って説明していきます。
前提条件
この記事を理解するためには、以下の知識が必要です:
- 基礎的なWEBの仕組みやJavaScriptの理解
Astroとは
Astroは、ブログやマーケティング、eコマースなど、コンテンツ駆動のウェブサイトを作成するためのウェブフレームワークです。Astroは、新しいフロントエンドアーキテクチャを開拓し、他のフレームワークと比較してJavaScriptのオーバーヘッドと複雑さを低減することで知られています。高速でSEOに優れたウェブサイトが必要なら、Astroが最適です。
公式サイト: https://astro.build/
Astroブログの仕組み
なぜAstroでブログを作るのか
Astroでは簡単にブログサイトを作ることができます。npm create astro@latest
などのコマンドでプロジェクトの雛形を作る際にblogテンプレートを選択すれば、すぐに簡易的なブログが出来上がります。
しかし、カスタマイズするためには内部の仕組みを理解する必要があります。ここでは、その仕組みを詳しく解説していきます。
ディレクトリ構成
Astroブログの一般的なディレクトリ構成は以下の通りです:
project-root/
├── src/ # ソースコードのルートディレクトリ
│ ├── components/ # UIコンポーネント
│ ├── content/ # コンテンツ(mdファイルなど)
│ │ └── blog/ # ブログ記事のmdファイル
│ │ ├── first-post.md
│ │ ├── second-post.md
│ │ └── third-post.md
│ ├── layouts/ # ページのレイアウト
│ ├── pages/ # 表示するページ
│ │ ├── blog/
│ │ │ └── [...slug].astro # ブログ詳細ページ(動的ルーティング)
│ │ └── index.astro # ブログ一覧ページ
│ ├── styles/ # スタイルシート
│ ├── consts.ts # 定数
│ └── content.config.ts # コンテンツコレクションの設定
コンテンツコレクションとは
コンテンツコレクションは、Astroプロジェクトでコンテンツを管理するための最適な方法です。簡単に言えば、マークダウンファイルなどのコンテンツを構造化されたデータとして扱い、簡単に取得・操作できるようにする仕組みです。
データベースのORMに似た機能を持ち、コンテンツを簡単に取得、フィルタリング、ソートすることができます。
実装解説
1. コンテンツコレクションの設定
まず、コンテンツコレクションの設定を行います。これにより、マークダウンファイルのフロントマターでどのようなデータを管理するか定義します。
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
// src/content/blog/ ディレクトリのMarkdownとMDXファイルを読み込む
type: 'content',
// スキーマを使ってフロントマターの型チェック
schema: z.object({
title: z.string(),
description: z.string(),
// 文字列からDateオブジェクトに変換
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
}),
});
export const collections = { blog };
この設定により、各ブログ記事のフロントマターに必要なフィールドと型が定義されます。
2. ブログ記事のマークダウンファイル
実際のブログ記事は、マークダウンファイルで作成します。
// src/content/blog/first-post.md
---
title: '最初の投稿'
description: '最初の投稿です。最初の投稿です。...'
pubDate: 'Jul 08 2022'
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit...
フロントマターには、コンテンツコレクションの設定で定義したフィールド(title, description, pubDate)を含める必要があります。
3. ブログ一覧ページの実装
次に、ブログの一覧ページを実装します。
// src/pages/index.astro
---
import BaseHead from '../components/BaseHead.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
import { getCollection } from 'astro:content';
import FormattedDate from '../components/FormattedDate.astro';
// ブログ記事を取得し、公開日の降順に並べ替え
const posts = (await getCollection('blog')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
</head>
<body>
<Header />
<main>
<section>
<ul>
{
posts.map((post) => (
<li>
<a href={`/blog/${post.slug}/`}>
<h4 class="title">{post.data.title}</h4>
<p class="description">{post.data.description}</p>
<p class="date">
<FormattedDate date={post.data.pubDate} />
</p>
</a>
</li>
))
}
</ul>
</section>
</main>
<Footer />
</body>
</html>
ここでの重要なポイント:
-
getCollection('blog')
でcontent/blog
ディレクトリ内のすべてのマークダウンファイルを取得 - 取得した記事データを公開日の降順にソート
- map関数を使って記事のリストを表示
4. ブログ詳細ページの実装(動的ルーティング)
最後に、ブログの詳細ページを実装します。これは動的ルーティングを使用して、各記事に対して個別のページを生成します。
// src/pages/blog/[...slug].astro
---
import { getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
return blogEntries.map((entry) => ({
params: { slug: entry.slug },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<BlogPost {...entry.data}>
<Content />
</BlogPost>
ブログ記事の詳細ページのテンプレート:
// src/layouts/BlogPost.astro
---
import type { CollectionEntry } from 'astro:content';
import BaseHead from '../components/BaseHead.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import FormattedDate from '../components/FormattedDate.astro';
type Props = CollectionEntry<'blog'>['data'];
const { title, description, pubDate, updatedDate } = Astro.props;
---
<html lang="en">
<head>
<BaseHead title={title} description={description} />
</head>
<body>
<Header />
<main>
<article>
<div class="prose">
<div class="title">
<div class="date">
{pubDate && <FormattedDate date={pubDate} />}
{
updatedDate && (
<div class="last-updated-on">
Last updated on <FormattedDate date={updatedDate} />
</div>
)
}
</div>
<h1>{title}</h1>
<hr />
</div>
<slot />
</div>
</article>
</main>
<Footer />
</body>
</html>
ここでの重要なポイント:
-
getStaticPaths
関数で、ビルド時に生成するすべての記事ページを定義 - 各記事のスラッグ(URLの一部)とそのコンテンツを取得
-
[...slug].astro
というファイル名でダイナミックルーティングを実現 -
entry.render()
で記事のコンテンツをレンダリング - レイアウトコンポーネントにデータを渡し、
<slot />
で記事のコンテンツを表示
動的ルーティングの仕組み
Astroでは、[...slug].astro
のようなファイル名を使用して動的ルーティングを実現します。角括弧([]
)内の名前がパラメータになり、...
(スプレッド演算子)を使うことで、その後のパスセグメントをすべてキャプチャします。
getStaticPaths
関数は、これらの動的ルートに対して、ビルド時に生成する実際のパスを定義します。ブログ記事の場合、各記事のスラッグに対応するパスを返すことで、静的なHTMLファイルが生成されます。
Astroのレンダリング方式
Astroはデフォルトで静的サイト生成(SSG)を採用しています。これにより、ビルド時にすべてのページが生成され、高速な読み込みとSEO最適化が実現します。
また、Astroはサーバーサイドレンダリング(SSR)と静的サイト生成(SSG)を組み合わせたハイブリッドレンダリングもサポートしており、動的なコンテンツと静的なコンテンツを最適な方法で組み合わせることができます。
まとめ
Astroを使うと、わずかな時間で高速でSEOに強いブログサイトを構築できます。特徴をまとめると:
- 簡単な導入: テンプレートを使って素早くブログを立ち上げられる
- コンテンツコレクション: マークダウンファイルを効率的に管理・操作できる
- 動的ルーティング: 記事ごとにURLを動的に生成
- 高速なパフォーマンス: デフォルトで静的サイト生成を採用
- 柔軟なカスタマイズ: 必要に応じてSSRとSSGを組み合わせることも可能
個人ブログならヘッドレスCMS + Astro(SSG)という組み合わせがベストであり、中規模以上のメディアでは、SSRとSSGを組み合わせたハイブリッドアプローチが効果的でしょう。
WordPressのような従来のCMSと比較して、Astroは表示速度とSEOの面で大きなアドバンテージがあります。プログラミングの基礎知識があれば、高品質なブログサイトが簡単に作れるので、ぜひ挑戦してみてください!
Discussion