Astro v6 でポートフォリオサイトを構築した話

astrotailwindcsscloudflarefrontend

このサイトは 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.tsglob ローダーを使い、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 フィードの実装なども検討しています。

← 記事一覧に戻る