Next.jsを静的ページを出力する話

今回は、Next.jsを静的ページ(html)に出力する方法をメモしておきます

対象: Next.js 16.4.3

Next.jsプロジェクトを作成する

Next,jsプロジェクトの作成は以下のコマンドで作成します

npx create-next-app@latest

プロジェクトの項目を選択して作成を完了させます

デフォルトプロジェクトをbuildしてみる

はい、何も変更しない状態で静的ページを出力させたいと思います

npm run build

すると…

npm notice run learn_nextjs@0.1.0 build
npm notice run next build
▲ Next.js 16.4.2 (Turbopack)
✓ Running next.config.ts took 30ms
- Cache Components enabled
- Partial Prefetching enabled
- Experiments (use with caution):
  ✓ agentFeedback

  Creating an optimized production build ...
✓ Compiled successfully in 203ms
  Running TypeScript ...
  Finished TypeScript in 1209ms ...
  Collecting page data using 5 workers ...
✓ Collecting page data using 5 workers in 417ms
  Generating static pages using 5 workers (0/4) ...
  Generating static pages using 5 workers (1/4) 
  Generating static pages using 5 workers (2/4) 
  Generating static pages using 5 workers (3/4) 
✓ Generating static pages using 5 workers (4/4) in 485ms
  Finalizing page optimization ...
✓ Finalizing page optimization in 7ms

Route (app)
┌ ○ /
└ ○ /_not-found


○  (Static)  prerendered as static content

正常に終了されて、staticファイルが生成しました的な表記になりますが、出力ディレクトリ(out)がありません

原因は出力設定がされていないためです

早速設定を行い出力を設定します

静的出力設定を行う

next.config.ts ファイルのデフォルト状態は以下です

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  experimental: {
    agentFeedback: true,
  },
  cacheComponents: true,
  partialPrefetching: true,
  turbopack: {
    rules: {
      "*.css": {
        loaders: ["@tailwindcss/turbopack"],
        as: "*.css",
      },
    },
  },
};

export default nextConfig;

この設定から静的ファイルの書き出し設定 output:”export” を追加します

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  output: 'export',
  experimental: {
    agentFeedback: true,
  },
  cacheComponents: true,
  partialPrefetching: true,
  turbopack: {
    rules: {
      "*.css": {
        loaders: ["@tailwindcss/turbopack"],
        as: "*.css",
      },
    },
  },
};

export default nextConfig;

再度 buildを実行します…

npm notice run learn_nextjs@0.1.0 build
npm notice run next build
▲ Next.js 16.4.2 (Turbopack)
✓ Running next.config.ts took 31ms
- Cache Components enabled
- Partial Prefetching enabled
- Experiments (use with caution):
  ✓ agentFeedback

  Creating an optimized production build ...
✓ Compiled successfully in 1189ms
  Running TypeScript ...
  Finished TypeScript in 1328ms ...
  Collecting page data using 5 workers ...
✓ Collecting page data using 5 workers in 508ms
  Generating static pages using 5 workers (0/4) ...
  Generating static pages using 5 workers (1/4) 
  Generating static pages using 5 workers (2/4) 
  Generating static pages using 5 workers (3/4) 
✓ Generating static pages using 5 workers (4/4) in 618ms
  Finalizing page optimization ...

> Build error occurred
Error: Invariant: PPR cannot be enabled in export mode
    at ignore-listed frames

はい、来ました。Buildエラーになります

Buildエラーの解消方法

デフォルトのプロジェクトではPPR(partialPrefetching)が有効になっています。
そのため出力できないよと言うことです。

partialPrefetchingってなんでしょう?

partialPrefetching は、アプリレベルで部分プリフェッチ(Partial Prefetching)を有効にします。フレームワークはデフォルトで各ルートの静的な部分をプリフェッチしますが、個々のリンクに prefetch={true} を設定すると、リンクごとのプリフェッチが使用され、より多くのデータを取得できます。

https://nextjs.org/docs/app/api-reference/config/next-config-js/partialPrefetching

要するに

リンクのプリフェッチされるようです。つまりは普通のhtmlでは使えない機能ですね

PPRを無効にしてbuildしてみる

先ほど調べたPPR(partialPrefetching)ですが、設定ファイル(next.config.ts)に明確にtrueになっています

それを無効にします

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  output: 'export',
  experimental: {
    agentFeedback: true,
  },
  cacheComponents: true,
  partialPrefetching: false,
  turbopack: {
    rules: {
      "*.css": {
        loaders: ["@tailwindcss/turbopack"],
        as: "*.css",
      },
    },
  },
};

export default nextConfig;

実行してもPPRが有効だとエラーになります

  Finalizing page optimization ...

> Build error occurred
Error: Invariant: PPR cannot be enabled in export mode
    at ignore-listed frames

先ほどのと同じですね

問題はcacheComponentsです

Cache Components は、use cache ディレクティブを使って、コンポーネントレベルおよび関数レベルのキャッシュを可能にします。データ取得はデフォルトで動的であり、ページ、コンポーネント、または関数のレベルで何をキャッシュするかを自由に選べます。Next.js は静的な HTML シェルを事前にレンダリングして即座に配信し、動的なコンテンツは準備ができ次第ストリーミングされるため、1つのルート内で静的なコンテンツと動的なコンテンツを混在させることができます。

partialPrefetching は明示的に設定してください。未設定のままにすると警告がログに出力されます。これは、Cache Components の最初のリリースには Partial Prefetching が含まれていなかったためです。false に設定するのは、Partial Prefetching の登場前に Cache Components を導入したアプリを移行している間に限ってください。次のメジャーリリースでは、両方の機能がすべての環境で有効になり、これらのオプションは削除されます。

https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents

要するに

cacheComponentsはpartialPrefetchingが必要なわけです(build時にPPRが有効になるかな)

なので、明示的にこの二つの設定を無効にします

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  output: 'export',
  experimental: {
    agentFeedback: true,
  },
  cacheComponents: false,
  partialPrefetching: false,
  turbopack: {
    rules: {
      "*.css": {
        loaders: ["@tailwindcss/turbopack"],
        as: "*.css",
      },
    },
  },
};

export default nextConfig;

buldを行うと正常に出力されたようです

npm notice run learn_nextjs@0.1.0 build
npm notice run next build
▲ Next.js 16.4.2 (Turbopack)
✓ Running next.config.ts took 32ms
- Experiments (use with caution):
  ✓ agentFeedback

  Creating an optimized production build ...
✓ Compiled successfully in 1048ms
  Running TypeScript ...
  Finished TypeScript in 1275ms ...
  Collecting page data using 5 workers ...
✓ Collecting page data using 5 workers in 430ms
  Generating static pages using 5 workers (0/4) ...
  Generating static pages using 5 workers (1/4) 
  Generating static pages using 5 workers (2/4) 
  Generating static pages using 5 workers (3/4) 
✓ Generating static pages using 5 workers (4/4) in 477ms
  Finalizing page optimization ...
✓ Finalizing page optimization in 531ms

Route (app)
┌ ○ /
└ ○ /_not-found


○  (Static)  prerendered as static content

出力先は out ディレクトリ

正常にstatic contentが作成されると以下のディレクトリに出力されます

out
├── __next.__PAGE__.txt
├── __next._full.txt
├── __next._tree.txt
├── _next
│   ├── static
│   │   ├── chunks
│   │   ├── media
│   │   └── w9TrsHlx8onnPaS_SaRW2
│   └── w9TrsHlx8onnPaS_SaRW2
├── _not-found
│   ├── __next._full.txt
├── _not-found.html
├── _not-found.txt
├── 404.html
├── favicon.ico
├── file.svg
├── globe.svg
├── index.html
├── index.txt
├── next.svg
├── vercel.svg
└── window.svg

ざっくりこれだけ生成されます。

index.htmlをWebサーバにドキュメントルートとして表示するとNext.jsのページが表示されます

npx serve@latest out
npm notice run learn_nextjs@0.1.0 npx
npm notice run 'serve' out

   │   Serving!                                       │
   │   - Local:    http://localhost:58270             │

ブラウザでhttp://localhost:58270 にアクセスすると動作できました

その他

はい、このままでもOKですが、このままだとrootにある前提なのでサブディレクトリには展開できません

Build結果のソースをみると分かりますが、

<link rel="preload" href="/_next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2" 

とPathが / 直下から見ます
サブディレクトリに静的ファイルを配置してもルートの_nextを参照するため動作しません

assetPrefixにてhrefのプリフィックスを指定する

assetPrefix: ‘.’,を指定する

assetPrefixを使用すると参照パスに./から始まることによりサブディレクトリからも参照できます

<link rel="preload" href="./_next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2" 

https://nextjs.org/docs/app/api-reference/config/next-config-js/assetPrefix

basePathを指定する

basePathを設定するとサブディレクトリの指定できます

basePath: ‘/sample’,と指定すると以下です。

すでにリリースするサブディレクトリが決定している場合はこれが良い選択です

<link rel="preload" href="/sample/_next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2"

課題

これで十分だとは思いますが、まだ、課題があります
それはLinkの参照パスがルートのままなのです

https://www.mmpp.org/react_sample/uml

サンプルページを確認すると思いますが、Sampleのリンクを押下して umlディレクトリに参照させたいのですが、/umlに飛んでページが見つからない動作になっています
これが改善できれば静的ページができるぞ!って言えそうですね

終いに

今回はNext.jsをcreateプロジェクトした生状態で静的ファイル出力ができない問題と解消方法をメモしておきました

と言うのも、先のUML出力ページを参照していたのですが、静的ファイル出力を行う時に…

できない…

設定をいじっても…

ってかなり悩みました

試行錯誤するうちに、そもそもデフォルト状態のNext,jsは静的ファイルが出力できたよね…

から始まりました

今後は課題にあります。
 - 静的ファイル出力するとuml.htmlが出力されている。しかし、index.htmlの内容では/umlを見に行くという問題も含まれています

この辺り、基本的な部分を考えたいですね

参考

https://nextjs.org/docs/app/guides/static-exports