Astro v6 でポートフォリオサイトを構築した話
このサイトは Astro v6 を使って構築した個人ブログ兼ポートフォリオサイトです。技術選定の理由や構成、つまずいたポイントについて書いていきます。
技術スタック
| カテゴリ | 技術 |
|---|---|
| フレームワーク | Astro v6 |
| スタイリング | Tailwind CSS v4 |
| コンテンツ | Markdown / MDX |
| デプロイ先 | Cloudflare Workers |
| パッケージマネージャ | pnpm |
| 言語 | TypeScript |
サイト構成
src/
├── pages/ # ファイルベースルーティング
│ ├── index.astro
│ ├── posts/ # ブログ記事ページ
│ └── works/ # ポートフォリオページ
├── components/ # 共通コンポーネント
├── layouts/ # レイアウト(Layout, PostLayout)
├── content/ # コンテンツコレクション
│ ├── posts/ # ブログ記事 (MD/MDX)
│ └── projects/ # プロジェクト紹介 (MD/MDX)
└── styles/ # グローバルスタイル
Astro のファイルベースルーティングにより、pages/ 配下のディレクトリ構造がそのまま URL 構造に対応します。ブログ記事とポートフォリオは [...slug].astro による動的ルーティングで、コンテンツコレクションから自動生成しています。
Astro v6 で変わったこと
Content Layer API と glob ローダー
Astro v6 では Content Layer API が正式に導入され、コンテンツコレクションの定義方法が変わりました。src/content.config.ts で glob ローダーを使い、Markdown ファイルを型安全に管理しています。
import { defineCollection } from "astro:content";
import { z } from "astro/zod";
import { glob } from "astro/loaders";
const posts = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/posts" }),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
image: z.string().optional(),
draft: z.boolean().default(false),
}),
});
スキーマ定義に使う Zod は Astro v6 に同梱されているため、別途インストールする必要がありません。z.coerce.date() で frontmatter の日付文字列を自動的に Date オブジェクトへ変換してくれるのも便利です。
ClientRouter によるページ遷移
Astro v6 では View Transitions API のラッパーとして ClientRouter コンポーネントが提供されています。
---
import { ClientRouter } from 'astro:transitions';
---
<head>
<ClientRouter />
</head>
これを <head> に追加するだけで、ページ遷移時にフルリロードではなくクライアントサイドナビゲーションが有効になり、スムーズなトランジションが実現できます。
Tailwind CSS v4 の CSS ファースト設定
Tailwind CSS v4 では tailwind.config.js が不要になり、CSS ファイル内で直接テーマを定義します。
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-bg: #f5f5f5;
--color-text: #1a1a2e;
--color-accent: #6c63ff;
/* ... */
}
Vite プラグインとして組み込むだけで動作するため、設定ファイルの管理が不要になったのは嬉しいポイントです。
// astro.config.mjs
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
});
Cloudflare Workers へのデプロイ
ビルド成果物は dist/ ディレクトリに出力されるため、Wrangler を使って Cloudflare Workers にデプロイしています。
// wrangler.jsonc
{
"name": "kurokoi-pf",
"assets": {
"directory": "./dist"
}
}
Astro の SSG(静的サイト生成)で全ページが事前にビルドされるため、サーバーサイドの処理は不要です。静的ファイルを配信するだけなら Cloudflare Pages でも十分ですが、Cloudflare は Pages と Workers を統合する方針を発表しており、公式ドキュメントでも Pages から Workers への移行ガイドが提供されています。将来的に Workers の機能(KV、D1、R2 など)を活用する拡張性も考慮して、最初から Workers を選択しました。
デプロイ手順は以下の通りです。
pnpm build # Astro でビルド
npx wrangler deploy # Cloudflare Workers にデプロイ
つまずいたポイントと学び
.astro ファイルの TypeScript 警告(TS7026)
プロジェクト作成直後、.astro ファイルで大量の TypeScript 警告が発生しました。これは Astro が生成する型定義ファイルが存在しないために起こるもので、以下のコマンドで解消できます。
npx astro sync
astro sync はコンテンツコレクションの型定義を .astro/ ディレクトリに生成してくれるコマンドです。エディタで型エラーが出たらまずこれを実行すると良いです。
ダークモードの状態管理と ClientRouter の共存
ClientRouter を使うとページ遷移時に通常の DOMContentLoaded が発火しません。そのためダークモードの切り替え状態が遷移後にリセットされる問題がありました。
解決策として、astro:after-swap イベントを利用して遷移後に localStorage からテーマを復元する処理を追加しました。
<script is:inline>
document.addEventListener("astro:after-swap", () => {
const theme = localStorage.getItem("theme");
document.documentElement.classList.toggle("dark", theme === "dark");
});
</script>
is:inline ディレクティブにより、このスクリプトはバンドルされずにそのまま HTML に埋め込まれます。これはページ描画前にテーマを適用し、ちらつきを防ぐために重要です。
Tailwind v4 でのダークモード設定
Tailwind CSS v4 ではダークモードの設定方法も変わりました。v3 までの darkMode: 'class' という設定は不要になり、@custom-variant で定義します。
@custom-variant dark (&:where(.dark, .dark *));
これにより dark:bg-dark-bg のようなユーティリティクラスが、.dark クラスのある要素とその子孫に対して適用されるようになります。
まとめ
Astro v6 + Tailwind CSS v4 の組み合わせは、どちらも設定がシンプルになった最新バージョンということもあり、快適な開発体験でした。Cloudflare Pages との相性も良く、ビルドからデプロイまでの流れがスムーズです。
今後はブログ記事の追加やポートフォリオの充実に加えて、OGP 画像の自動生成や RSS フィードの実装なども検討しています。