Next.js 静的エクスポート徹底解説:200+ ページの高性能ツールサイトの構築

技术架构(更新: 2026年5月17日)

静的エクスポートの仕組み

Next.js の output: "export" はアプリ全体を純粋な静的 HTML に事前レンダリングし、Node.js ランタイムは不要です。

next build (SSG)
     ↓
すべての generateStaticParams() を走査
     ↓
各 [locale] × [slug] の組み合わせで HTML を事前生成
     ↓
out/ ディレクトリへ出力
     ↓
OSS/CDN にアップロード

SSG vs SSR vs ISR

モード サーバー要否 ビルド時レンダリング 更新方法 向いている用途
SSG (export) 不要 再ビルド&デプロイ 内容が固定のツールサイト
SSR 必要 リクエストごとにレンダリング パーソナライズされたコンテンツ
ISR 必要 バックグラウンドで段階的再生成 頻繁に更新されるコンテンツ

ToolsKu が SSG を選んだ理由:

  • 200+ のツールページは完全に静的で動的データが不要
  • 阿里云 OSS + CDN にデプロイし、運用コストゼロ
  • 初回 TTFB が極めて低い(CDN が HTML を直接返す)
  • オフラインキャッシュに自然に適合

generateStaticParams の設計パターン

多言語 × ツールページの直積

// src/app/[locale]/layout.tsx
export function generateStaticParams() {
  return [
    { locale: 'zh-CN' },
    { locale: 'zh-TW' },
    { locale: 'en' },
    { locale: 'ja' },
  ];
}
// src/app/[locale]/pdf/merge/page.tsx
export function generateStaticParams() {
  return [
    { locale: 'zh-CN' },
    { locale: 'zh-TW' },
    { locale: 'en' },
    { locale: 'ja' },
  ];
}

Next.js は layout.generateStaticParams × page.generateStaticParams直積を自動計算し、各 (locale, path) 組み合わせの HTML を生成します。

動的ルート:ブログ記事の列挙

// src/app/[locale]/blog/[slug]/page.tsx
export function generateStaticParams() {
  return getStaticParamsForAllPosts().map(({ slug, locale }) => ({
    locale,
    slug,
  }));
}

getStaticParamsForAllPosts()src/content/blog/ をスキャンし、すべての slug × 利用可能な言語を列挙して、各記事の各言語版を事前レンダリングします。


ビルド性能の最適化

大規模サイトでのビルド課題

  • 292 ツールページ × 4 言語 = 1168 HTML ページ
  • ブログ・カテゴリページなどを含め 1300+ 静的ページ
  • 各ページで getTranslations() により i18n メッセージを読み込み

並行度の制御

// next.config.ts
experimental: {
  staticGenerationRetryCount: 2,
  ...(isCi
    ? {
        staticGenerationMaxConcurrency: 4,     // CI: 4 並列
        staticGenerationMinPagesPerWorker: 32,  // worker あたり 32 ページ
      }
    : {
        staticGenerationMaxConcurrency: 16,     // ローカル: 16 並列
        staticGenerationMinPagesPerWorker: 8,
      }),
}
  • CI 環境:GitHub Actions のメモリが限られるため、OOM 回避のため並行度を下げる
  • ローカル開発:並行度を上げてビルドを高速化

タイムアウト設定

staticPageGenerationTimeout: 180, // 3 分

一部のツールページ(動画ツールなど)は WASM 依存が多く、事前レンダリングがデフォルトの 60 秒を超えることがあります。


多言語ルーティングアーキテクチャ

next-intl + App Router の統合

src/i18n/routing-config.ts
  → locales: ['zh-CN', 'zh-TW', 'en', 'ja']
  → localePrefix: 'always'  ← すべての URL に言語プレフィックス
  → localeDetection: true   ← Accept-Language による自動協調

URL 構造

https://www.toolsku.com/zh-CN/pdf/merge/     ← 簡体字中国語
https://www.toolsku.com/en/pdf/merge/         ← 英語
https://www.toolsku.com/zh-CN/blog/pdf-merge-guide/  ← ブログ

hreflang の実装

各ページに完全な hreflang タグを注入:

<link rel="alternate" hreflang="zh-CN" href="https://www.toolsku.com/zh-CN/pdf/merge/" />
<link rel="alternate" hreflang="zh-TW" href="https://www.toolsku.com/zh-TW/pdf/merge/" />
<link rel="alternate" hreflang="en" href="https://www.toolsku.com/en/pdf/merge/" />
<link rel="alternate" hreflang="ja" href="https://www.toolsku.com/ja/pdf/merge/" />
<link rel="alternate" hreflang="x-default" href="https://www.toolsku.com/zh-CN/pdf/merge/" />

Sitemap の自動検出

ToolsKu の sitemap.ts はファイルシステムスキャンで全ページを自動検出します。

const pathEntries = discoverLocaleInnerPathsWithFilePath(appDir);
// src/app/[locale]/ 配下のすべての page.tsx を自動検出

動的セグメントのスキップ

discoverLocaleInnerPaths は設計上動的セグメント[slug] など)をスキップします。パラメータがないとレンダリングできないためです。

ブログ記事は sitemap.ts で別途処理します:

const blogSlugs = getAllBlogSlugs();
for (const slug of blogSlugs) {
  entries.push({
    url: absoluteLocalizedUrl(locale, `/blog/${slug}`),
    lastModified: meta.updatedAt,
    priority: 0.7,
    alternates: { languages },
  });
}

CDN デプロイアーキテクチャ

アセットプレフィックス (Asset Prefix)

// next.config.ts
assetPrefix: getAssetPrefix(),
// → 本番は https://toolsku.oss-cn-beijing.aliyuncs.com/public

ビルド成果物の _next/static/ 配下の JS/CSS は CDN を指し、HTML は OSS から直接配信されます。

デプロイフロー

next build → out/
     ↓
ossutil cp out/ oss://toolsku/ --recursive
     ↓
CDN キャッシュ無効化(または TTL 待ち)
     ↓
新バージョン公開

キャッシュ戦略

リソース種別 キャッシュ戦略 理由
_next/static/**/*.js Cache-Control: max-age=31536000, immutable ファイル名に hash
*.html Cache-Control: max-age=3600, s-maxage=86400 ページは更新されうる
*.wasm Cache-Control: max-age=31536000 WASM バージョンは安定
i18n/*.json Cache-Control: max-age=86400 検索インデックスは定期更新

ビルドパイプライン

yarn build
  → generate-tools-search-index.mjs    ← ツール検索インデックス生成
  → generate-tool-clients-public.mjs   ← ツールクライアントデータ生成
  → next build                          ← 全ページを SSG 事前レンダリング
     → generateStaticParams() を走査
     → 各 (locale, path) をレンダリング
     → out/ に出力

インクリメンタルビルドの課題

Next.js の静的エクスポートは現時点でインクリメンタルビルド非対応です。next build のたびに全ページを再レンダリングします。1300+ ページではフルビルドに 3〜5 分かかります。

対策

  • CI では並行度を下げてメモリピークを抑制
  • ローカル開発は dev モード(オンデマンドレンダリング、事前生成なし)
  • Turbopack で開発モードを高速化

まとめ

決定事項 選択 理由
レンダリング SSG (export) 運用ゼロ、極低 TTFB
ルーティング App Router Server Components、優れたコード分割
国際化 next-intl App Router ネイティブ、充実したメッセージ
コンテンツ リポジトリ (JSON/TS) CMS 不要、Git でバージョン管理
デプロイ OSS + CDN 中国国内で高速、サーバー運用不要

純粋な静的エクスポートはツール系サイトに最適です。内容が固定でアクセスが多く、初回表示速度が重要だからです。Next.js App Router + SSG により、ToolsKu はサーバー運用なしで高性能なデプロイを実現しています。

ブラウザローカルツールを無料で試す →

#Next.js#SSG#静态导出#CDN#性能优化