ホーム 記事一覧 ブック DH週間トピックス 検索 このサイトについて
English
Nuxt 4 SSGでローカルJSONファイルを正しく読み込む方法

Nuxt 4 SSGでローカルJSONファイルを正しく読み込む方法

はじめに Nuxt 4でStatic Site Generation (SSG) を行う際、ローカルのJSONファイルからデータを読み込んで静的ページを生成したいケースがあります。しかし、Next.jsのgetStaticPropsのようにシンプルにはいかず、いくつかのハマりポイントがあります。 本記事では、試行錯誤の末に見つけた正しいアプローチを紹介します。 問題:なぜ単純なfsの読み込みでは動かないのか 最初に試したアプローチ(失敗) // ❌ これは動かない const fetchLocalData = async (filePath: string) => { if (import.meta.server) { const fs = await import('fs'); const path = await import('path'); const fullPath = path.resolve(process.cwd(), 'public/data', filePath); const data = fs.readFileSync(fullPath, 'utf-8'); return JSON.parse(data); } // クライアントサイド const response = await fetch(`/data/${filePath}`); return await response.json(); }; このアプローチには以下の問題があります: process.cwd()がビルド環境で異なる: ローカル開発とVercel等のビルド環境では作業ディレクトリが異なる Nitroのプリレンダリング時にファイルが見つからない : SSG時、Nitroは独自のコンテキストで動作する useAsyncDataなしでは、クライアントサイドでも実行される : SSGの意味がなくなる 解決策:Nitro Storage API + Server APIルート + useAsyncData アーキテクチャ [SSGビルド時] Page Component ↓ useAsyncData Server API Route (/api/local-data/[...path]) ↓ useStorage (Nitro Storage API) public/data/*.json [生成されたHTML] _payload.json にデータが埋め込まれる → クライアントサイドでのJSON読み込み不要 Step 1: nuxt.config.tsでserverAssetsを設定 // nuxt.config.ts export default defineNuxtConfig({ nitro: { prerender: { crawlLinks: true, failOnError: false, }, // Nitro Storage APIでpublic/dataをサーバーアセットとしてマウント serverAssets: [{ baseName: 'data', dir: './public/data' }], }, routeRules: { '/**': { prerender: true }, }, }); Step 2: Server APIルートを作成(Nitro Storage + fsフォールバック) // server/api/local-data/[...path].ts import { readFileSync, existsSync } from 'fs'; import { resolve } from 'path'; export default defineEventHandler(async (event) => { const pathParam = getRouterParam(event, 'path'); if (!pathParam) { throw createError({ statusCode: 400, message: 'Path is required' }); } const filePath = Array.isArray(pathParam) ? pathParam.join('/') : pathParam; // セキュリティ: パストラバーサル防止 if (filePath.includes('..')) { throw createError({ statusCode: 400, message: 'Invalid path' }); } // Nitro Storage APIを使用 // nuxt.config.tsのserverAssetsで定義した'data'ストレージにアクセス const storage = useStorage('assets:data'); // ファイルパスをストレージキーに変換(/を:に置換) const storageKey = filePath.replace(/\//g, ':'); // Storage APIで取得を試みる if (await storage.hasItem(storageKey)) { const data = await storage.getItem(storageKey); return data; } // フォールバック: 開発環境向けにfsで直接読み込み const fsPath = resolve(process.cwd(), 'public/data', filePath); if (existsSync(fsPath)) { const data = readFileSync(fsPath, 'utf-8'); return JSON.parse(data); } throw createError({ statusCode: 404, message: `File not found: ${filePath}` }); }); Nitro Storage APIを使うメリット: ...

Next.js 15対応 多言語・ダークモード対応SSGテンプレート

Next.js 15対応 多言語・ダークモード対応SSGテンプレート

この記事は人間が実装を確認し、AIが記事を作成しました。 概要 このテンプレートは、Next.js 15を使用した静的サイト生成(SSG)に対応し、多言語対応とダークモードを標準装備したWebアプリケーション開発の出発点です。TypeScript、Tailwind CSS、next-intl、next-themesを組み合わせています。 https://nextjs-i18n-themes-ssg-template.vercel.app/ja/ 主な機能 1. 静的サイト生成(SSG) output: 'export'によるフルスタティックエクスポート 高速なページロードとSEO最適化 ホスティングコストの削減 2. 国際化対応(i18n) next-intlによる完全な多言語サポート 日本語・英語対応(簡単に言語追加可能) URLベースの言語切り替え(/ja/about、/en/about) 型安全な翻訳キー 3. ダークモード next-themesによるシステム連動ダークモード ユーザーの好みを自動検出 スムーズなテーマ切り替えアニメーション LocalStorageによる設定の永続化 4. 開発者体験の向上 TypeScriptによる型安全性 Tailwind CSSによる効率的なスタイリング ESLintによるコード品質管理 統一されたコンポーネント構造 技術スタック { "dependencies": { "next": "^15.4.4", "react": "^19.1.0", "next-intl": "^4.3.4", "next-themes": "^0.4.6", "tailwindcss": "^4.1.11", "@tailwindcss/typography": "^0.5.16" } } プロジェクト構造 src/ ├── app/ │ ├── [locale]/ │ │ ├── layout.tsx # ルートレイアウト │ │ ├── page.tsx # ホームページ │ │ ├── about/ # Aboutページ │ │ └── example/ # サンプルページ │ ├── icon.svg # ファビコン │ └── sitemap.ts # サイトマップ生成 ├── components/ │ ├── layout/ # レイアウトコンポーネント │ │ ├── Header.tsx │ │ ├── Footer.tsx │ │ ├── PageLayout.tsx │ │ ├── ToggleTheme.tsx │ │ └── ToggleLanguage.tsx │ └── page/ # ページ固有コンポーネント ├── i18n/ │ └── routing.ts # i18n設定 └── messages/ # 翻訳ファイル ├── en.json └── ja.json 特徴的な実装 1. sitemap.ts の静的エクスポート対応 export const dynamic = 'force-static'; export const revalidate = false; export default function sitemap(): MetadataRoute.Sitemap { // 実装 } 2. 統一されたページレイアウト <PageLayout breadcrumbItems={breadcrumbItems} title={t('title')} description={t('description')} > <YourContent /> </PageLayout> 3. 環境変数による設定 # .env.example NEXT_PUBLIC_SITE_URL=http://localhost:3000 NEXT_PUBLIC_BASE_PATH= 使い方 インストール git clone [repository-url] cd nextjs-i18n-themes-ssg-template npm install 開発 npm run dev ビルド npm run build カスタマイズポイント 言語追加 : src/i18n/routing.tsとmessages/ディレクトリ ページ追加 : src/app/[locale]/配下に新規ディレクトリ テーマカスタマイズ : tailwind.config.jsとグローバルCSS メタデータ : 各ページのgenerateMetadata関数 ベストプラクティス コンポーネント命名 : PascalCaseを使用 翻訳キー : ネストした構造で整理 型安全性 : TypeScriptの型を最大限活用 パフォーマンス : 静的生成を活用したキャッシュ戦略 まとめ 国際化対応とダークモード機能を標準装備し、SEOに最適化された静的サイトを素早く構築できるよう目指しています。開発者の生産性を向上させながら、エンドユーザーに優れた体験を提供していきたいと思います。 ...

Next.js 15 で output: 'export' 使用時の sitemap.ts 実装方法

Next.js 15 で output: 'export' 使用時の sitemap.ts 実装方法

この記事は人間が実装を確認したのち、AIが記事を執筆しました。 背景 Next.js 15で静的サイト生成(output: 'export')を使用する際、sitemap.tsの実装でエラーが発生する場合があります。 Error: export const dynamic = "force-static"/export const revalidate not configured on route "/sitemap.xml" with "output: export". 解決方法 この問題は、sitemap.tsに以下の2つのエクスポートを追加することで解決できます: // src/app/sitemap.ts import { MetadataRoute } from 'next'; export const dynamic = 'force-static'; export const revalidate = false; export default function sitemap(): MetadataRoute.Sitemap { // sitemap生成ロジック } 実装例 import { MetadataRoute } from 'next'; import { routing } from '@/i18n/routing'; export const dynamic = 'force-static'; export const revalidate = false; export default function sitemap(): MetadataRoute.Sitemap { const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'; const staticPages = [ '', // Home page '/about', '/example', ]; const sitemapEntries: MetadataRoute.Sitemap = routing.locales.flatMap((locale) => staticPages.map((page) => ({ url: `${baseUrl}/${locale}${page}`, lastModified: new Date(), changeFrequency: page === '' ? 'daily' : 'weekly' as const, priority: page === '' ? 1 : 0.8, })) ); return sitemapEntries; } 動作確認 この実装により、npm run build実行時に/out/sitemap.xmlが正常に生成されます。 注意点 dynamic = 'force-static'のみでは不十分で、revalidate = falseも必要です 環境変数やインポートしたモジュールの使用も可能です ビルド時に静的に解決される値であれば、動的な値も使用できます 代替案 もし上記の方法で解決しない場合は、以下の代替案があります: ...