個人ブログを Nuxt Content でリニューアルした話
Astro 5 から Nuxt Content v3 + UnoCSS + Vite+ に移行した経緯と技術スタックの紹介です。
はじめに
これまで Astro 5 で構築していた個人ブログを、Nuxt Content v3 を中心とした構成にリニューアルしました。
Astro は素晴らしいフレームワークですが、Vue エコシステムが好きな自分にとって、Vue コンポーネントを自然に書ける環境に移行したいと思っていました。
新しい技術スタック
コア
- Nuxt 4 - Vue フルスタックフレームワーク
- Nuxt Content v3 - SQLite ベースのコンテンツ管理
- Vite+ - Vite / Rolldown / Vitest / Oxlint を統合したツールチェーン
スタイリング
- UnoCSS - Tailwind 互換のオンデマンド CSS エンジン
デプロイ
- Cloudflare Workers - エッジでの SSR / 静的配信
Astro から Nuxt Content v3 への移行
コンテンツコレクションの違い
Astro の defineCollection と Nuxt Content v3 の defineCollection は似ていますが、スキーマ定義と取得 API が異なります。
Astro の場合
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blogCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.string().transform((str) => new Date(str)),
}),
});
Nuxt Content v3 の場合
// content.config.ts
import { defineContentConfig, defineCollection, z } from '@nuxt/content';
export default defineContentConfig({
collections: {
blog: defineCollection({
type: 'page',
source: 'blog/**',
schema: z.object({
title: z.string(),
date: z.string(),
}),
}),
},
});
Nuxt Content v3 はコレクションを SQLite に保存します。ビルド時にコンテンツを処理してデータベースを構築し、クエリを高速化しています。
コンテンツの取得
Astro の getCollection に対応するのが、Nuxt Content v3 の queryCollection です。
// Nuxt Content v3
const posts = await queryCollection('blog')
.order('date', 'DESC')
.all();
Vue ページ内では auto-import されるため、import 文が不要です。
ルーティング
Astro のファイルベースルーティングと同様に、Nuxt も pages/ ディレクトリでルーティングを管理します。
ブログ記事の詳細ページは pages/posts/[...slug].vue として定義し、パスからスラッグを取得してコンテンツをクエリしています。
// app/pages/posts/[...slug].vue
const route = useRoute();
const slug = Array.isArray(route.params.slug)
? route.params.slug.join('/')
: route.params.slug;
const { data: post } = await useAsyncData(`post-${slug}`, () =>
queryCollection('blog').path(`/blog/${slug}`).first()
);
UnoCSS への移行
Astro では Tailwind CSS v4 を使っていましたが、Nuxt への移行にあわせて UnoCSS に変更しました。
UnoCSS はオンデマンドで CSS を生成するため、使用したユーティリティクラスだけがバンドルに含まれます。presetWind4 で Tailwind v4 互換のユーティリティが使えます。
// uno.config.ts
import { defineConfig, presetWind4 } from 'unocss';
export default defineConfig({
presets: [presetWind4()],
});
Nuxt への組み込みは @unocss/nuxt モジュールで完結します。
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@unocss/nuxt', '@nuxt/content'],
});
Vite+ ツールチェーン
このプロジェクトでは Vite+ を使っています。Vite+は Vite / Rolldown / Vitest / Oxlint / Oxfmt を統合した vp コマンド 1 つで全てのツールを扱えるツールチェーンです。
# 開発サーバー起動
vp dev
# チェック(フォーマット・Lint・型チェック)
vp check
# テスト実行
vp test
個別にツールをインストール・設定する必要がなく、統一されたインターフェースでプロジェクトを管理できます。
ダークモード
システムの color-scheme に合わせた自動ダークモードを実装しています。
FOUC(コンテンツのちらつき)を防ぐため、インライン script を <head> 内で実行しています。
// nuxt.config.ts
app: {
head: {
script: [{
innerHTML: `
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList[isDark ? 'add' : 'remove']('dark');
`,
tagPosition: 'head',
}],
},
},
まとめ
Astro から Nuxt Content v3 への移行で、Vue エコシステムに統一された環境を構築できました。
- Nuxt Content v3 の SQLite ベースのクエリは高速で型安全
- UnoCSS のオンデマンド生成で CSS バンドルが最小化
- Vite+ で複数ツールの設定を一元管理
Vue が好きな方には Nuxt Content の組み合わせを強くおすすめします。