🚀
概念 #Astro #SSG #MPA #アイランドアーキテクチャ 📚 ブログサイト構築

ブログサイト構築 — Astro SSG と MPA 設計

Astro の静的サイト生成(SSG)・MPA・アイランドアーキテクチャの概念整理。SPAとの違いと本サイトでの採用判断

SPA / MPA / SSG の整理

SPA(Single Page Application)

クライアントが最初に空の HTML + JS バンドルを受け取り、以後のナビゲーションはすべて JS がルーティングを担う。

初回: server → index.html(ほぼ空)+ app.js(巨大)
以後: JS がルートを解析して仮想DOMを更新

利点: ページ遷移が高速・リッチなインタラクション
欠点: 初回 JS 読み込みが重い・SEO が難しい・クローラーへの追加対応が必要

MPA(Multi Page Application)

ページ遷移のたびにサーバーが完全な HTML を返す従来型の構成。

/blog → server → blog.html(完全な HTML)
/docs → server → docs.html(完全な HTML)

利点: SEO が強い・初期表示が速い・JS 障害耐性
欠点: ページ遷移のたびに全 HTML を再レンダリング

SSG(Static Site Generation)

ビルド時にすべてのページの HTML を生成し、CDN で配信する。

build time: Markdown + テンプレート → HTML(全ページ分)
runtime:    CDN がそのまま返す(サーバー処理なし)

利点: 高速(CDN キャッシュ)・安価・スケーラブル
欠点: 動的コンテンツ(リアルタイムデータ)が難しい

Astro の採用

本サイトは SSG ベースの MPA として Astro を採用。

pnpm build
  → src/pages/**/*.astro + src/content/**/*.md
  → dist/**/*.html(全記事ぶんの静的 HTML)
  → dist/ogp/**/*.png(全記事ぶんの OGP 画像)

npx wrangler pages deploy dist
  → Cloudflare Pages CDN に配信

なぜ SPA にしなかったか

ブログ・Docs サイトの主な用途は「検索エンジンからの流入」と「コンテンツの閲覧」。
SEO と初期表示速度が最優先であり、SPA のインタラクティビティは不要。

アイランドアーキテクチャ

Astro はデフォルトで JS を生成しない。 一部の UI だけをインタラクティブにしたいとき、Island として client:* ディレクティブを指定する。

<!-- サーバーサイド(ビルド時)レンダリング: JS なし -->
<StaticComponent />

<!-- クライアントサイド hydration: ページロード後に JS が有効化 -->
<InteractiveCounter client:load />

本サイトでのアイランド:

コンポーネントディレクティブ理由
ViewCounterインラインスクリプトfetch + localStorage
SpeechButtonインラインスクリプトWeb Speech API
テーマ切替インラインスクリプトCSS 変数の動的変更
検索 (Pagefind)<script> タグPagefind JS ライブラリ

Content Collections

Astro の Content Collections は src/content/ のファイルを型安全に扱う仕組み。

// src/content.config.ts
const blog = defineCollection({
  loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
  schema: z.object({
    title: z.string(),
    description: z.string().optional(),
    date: z.date(),
    tags: z.array(z.string()).default([]),
    image: z.string().optional(),
    series: z.array(z.string()).optional(),
  }),
});

ページでの利用:

// src/pages/blog/[...slug].astro
const posts = await getCollection('blog');
// posts は CollectionEntry<'blog'>[] 型で型チェック済み

この設計の利点:

  • frontmatter の型安全(スキーマ不一致はビルドエラー)
  • glob() loader で Markdown ファイルを自動収集
  • getCollection() で全記事を取得して静的パスを生成

ビルドパイプライン

pnpm build
  1. Astro: Markdown → HTML、getStaticPaths でページ列挙
  2. Satori + resvg: 各記事の OGP PNG をビルド時に生成
  3. @astrojs/sitemap: sitemap-index.xml を自動生成
  4. Pagefind: dist/ を走査して検索インデックスを構築

→ dist/ に成果物が出揃う

関連