Next.jsのアーキテクチャと思想の変遷
Next.jsは2016年の登場以来、Reactエコシステムにおける事実上の標準フレームワークへと成長した。しかしその道のりは単なる機能追加の歴史ではなく、Web開発のあるべき姿についての思想的な探求でもあった。
この記事では、Next.jsがどのような設計思想に基づいて生まれ、どのように進化してきたかを時系列で追いながら、各決定の背景にある「なぜ」を探っていく。
設計思想の原点(2016年)
Guillermo Rauchの6原則
Next.jsは2016年10月25日、Vercel(当時Zeit)の創業者Guillermo RauchによってオープンソースとしてGitHubに公開された。その設計は6つの原則に基づいていた:
- Zero Setup - 設定なしですぐに動く
- Filesystem as API - ファイルシステムがルーティングを定義する
- Only JavaScript - すべてが関数として表現される
- Automatic Code Splitting - 自動的なコード分割
- Configurable Data Fetching - 柔軟なデータ取得
- Anticipating Requests - リクエストの先読み
これらの原則の根底にあったのは、「開発者がインフラの複雑さから解放され、アイデアをすぐにオンラインに持っていける」という信念だった。
「サーバーに近いところで計算する」
Rauchは創業当初から一貫した信念を持っていた。
「物理法則により、レンダリングはサーバーサイドで、データベースに近い場所で行われるべきだ」
ユーザーがWebサイトを閲覧するとき、計算はできるだけデータベースに近い場所で行われ、コンテンツのストリームとしてクライアントに返されるべきだという考えだ。これは後のRSC(React Server Components)採用にも通じる思想である。
Progressive Disclosure of Complexity
Vercelの設計原則として重要なのが「複雑さの段階的開示(Progressive Disclosure of Complexity)」だ。初心者には簡単に見え、上級者には十分な深さを提供する。
Zero Configurationはその象徴だ。従来のReactアプリケーションでは、ルーティング、バンドリング、最適化に膨大な設定が必要だった。Next.jsはそれらにsensibleなデフォルトを提供し、必要に応じて上書きできる設計とした。
Pages Router時代(2016-2022)
ファイルシステムベースルーティング
Pages Routerの設計はシンプルだった。pages/ディレクトリにファイルを置けば、それがそのままURLになる。
pages/
├── index.js → /
├── about.js → /about
└── posts/
└── [id].js → /posts/:id
設定ファイルを書く必要がない。ファイルを作れば動く。これは「Filesystem as API」の具体化であり、多くの開発者に歓迎された。
データ取得の3つのパターン
Pages Routerでは、データ取得のタイミングを3つの関数で制御した:
| 関数 | 実行タイミング | 用途 |
|---|---|---|
getStaticProps |
ビルド時 | 静的生成(SSG) |
getServerSideProps |
リクエスト時 | サーバーサイドレンダリング(SSR) |
getStaticPaths |
ビルド時 | 動的ルートの事前生成 |
// pages/posts/[id].js
export async function getStaticProps({ params }) {
const post = await fetchPost(params.id)
return { props: { post } }
}
export async function getStaticPaths() {
const posts = await fetchAllPosts()
return {
paths: posts.map(post => ({ params: { id: post.id } })),
fallback: false
}
}
この設計は明快だったが、ページ単位でしかデータ取得のタイミングを制御できないという制約があった。
ISR(Incremental Static Regeneration)の革新
2020年、Next.js 9.5でISRが導入された。これは静的生成とサーバーサイドレンダリングの間を埋める画期的な機能だった。
export async function getStaticProps() {
const data = await fetchData()
return {
props: { data },
revalidate: 60 // 60秒後に再生成
}
}
ISRの仕組み:
- 初回リクエストでは静的に生成されたページを即座に返す
revalidateで指定した時間が経過すると、次のリクエスト時にバックグラウンドで再生成- 再生成完了後、新しいページがキャッシュされる
これにより「ビルドに時間がかかる大規模サイト」の問題を解決した。全ページを事前生成する必要がなくなり、必要に応じて増分的に生成できるようになった。
ただし、ISRはVercelプラットフォームと密接に結びついた機能でもあった。キャッシュの永続化、無効化、再生成トリガーはVercel固有のインフラに依存しており、他のプラットフォームでは同等の機能を得にくいという課題が生まれた。
ReactチームとVercelの協力関係
Sebastian MarkbågeのVercel参加
2021年末、React CoreチームのSebastian MarkbågeがMetaを離れVercelに参加した。これは単なる転職ではなく、Reactの未来を形作る戦略的な動きだった。
MarkbågeはVercelでの役割を担いながらも、React Coreチームのリーダーシップを継続し、Reactの方向性に影響を与え続けた。この人事異動は、ReactとNext.jsの境界を曖昧にしていく転換点となった。
RSCの共同開発
React Server Components(RSC)は、Reactチームが構想しMetaで研究を進めていた機能だった。しかし、Metaは独自の巨大なサーバーインフラを持ち、サードパーティのフレームワークをほとんど使わない。RSCを実世界で動かすには、外部のスポンサーが必要だった。
Vercelがその役割を担った。Next.jsチームは「膨大な時間、資金、エンジニアリング努力を費やして、RSCの最初の動作実装としてNext.js App Routerを設計・構築した」とされる。
「誰が誰を支配しているのか」
この協力関係は論争も呼んだ。「VercelがReactを支配している」という批判がある一方、Redux のメンテナーMark Eriksonは異なる見解を示している:
「VercelとNext.jsがReactを乗っ取ったというより、ReactチームがNext.jsを乗っ取ったのだ」
RSCは「ハイブリッド機能」であり、React本体だけでは完結しない。バンドラー、ルーター、フレームワークの追加設定が必要だ。ReactチームはRSCの完全な実装を単独では提供できず、Next.jsが必要だったという見方もできる。
どちらが正しいかはさておき、ReactとNext.jsは今や不可分の関係にある。Reactの公式ドキュメントがNext.jsを最初に推奨しているのは、その象徴だ。
App Router革命(2022-2024)
Pages Routerの限界
Pages Routerは成功を収めたが、アプリケーションが複雑化するにつれて限界が見えてきた:
- 複雑なレイアウト管理 - ネストされたルートや共有レイアウトの管理が煩雑
- ページ単位のデータ取得 - コンポーネント単位での最適化ができない
- ルート変更時の全体再レンダリング - パフォーマンス上の課題
- サーバー/クライアントの曖昧な境界 - どこで何が実行されるかが不明確
App Routerの登場
2022年10月、Next.js 13でApp Routerが発表された。これはPages Routerの改良ではなく、根本的なパラダイムシフトだった。
app/
├── layout.js # 共有レイアウト
├── page.js # /
├── about/
│ └── page.js # /about
└── posts/
├── layout.js # posts用レイアウト
└── [id]/
├── page.js # /posts/:id
├── loading.js # ローディングUI
└── error.js # エラーUI
主な変更点:
- React Server Componentsがデフォルト - コンポーネントはサーバーで実行される
- ネストされたレイアウト -
layout.jsによる階層的なUI構造 - ローディング・エラー状態の組み込み -
loading.js、error.jsによる宣言的な状態管理 - コンポーネント単位のデータ取得 - ページレベルではなくコンポーネントレベルで制御
RSCの設計思想
RSCの核心は「サーバーファースト」だ。コンポーネントはデフォルトでサーバーで実行され、クライアントには結果のHTMLだけが送られる。
// Server Component(デフォルト)
async function PostList() {
const posts = await db.posts.findMany() // サーバーで直接DB接続
return (
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
)
}
クライアント側の機能(useState、イベントハンドラなど)が必要な場合のみ、"use client"を宣言する。
"use client"
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
この設計により:
- バンドルサイズの削減 - サーバーコンポーネントのコードはクライアントに送られない
- 初期表示の高速化 - サーバーでレンダリングされたHTMLを即座に表示
- データベース直接アクセス - APIレイヤーを介さずにデータ取得
Provider問題とUIライブラリへの影響
App Routerの登場は、既存のUIライブラリに大きな影響を与えた。
従来のChakra UIやMUIといったライブラリは、アプリケーション全体をProviderでラップする設計を前提としていた。テーマ、カラーモード、アクセシビリティ設定などをグローバルに管理するためだ。
// 従来の設計
<ChakraProvider>
<ColorModeProvider>
<App />
</ColorModeProvider>
</ChakraProvider>
しかしReact Context(Providerの基盤)はクライアント専用機能であり、RSCとは本質的に相容れない。開発者は"use client"を宣言したファイルでProviderを設定するという「儀式」を強いられることになった。
この問題を背景に、shadcn-uiのようなProvider不要のアプローチが支持を集めた。Radix PrimitivesとTailwind CSSを組み合わせ、ランタイム依存を排除した設計は、App Router時代の最適解となった。
キャッシュモデルの変遷
Next.jsの歴史で最も論争を呼んだのがキャッシュの扱いだ。
v14: 暗黙的で積極的なキャッシュ
Next.js 14では、fetchリクエストはデフォルトでforce-cacheが適用され、データは無期限にキャッシュされた。
// v14: 暗黙的にキャッシュされる
const data = await fetch('https://api.example.com/data')
この設計の意図は「パフォーマンス最優先」だった。しかし現実には:
- データがいつ更新されるか予測できない
- 本番環境で古いデータが表示され続ける
- デバッグが困難
開発者は「キャッシュの混乱」に悩まされた。
v15: デフォルトuncachedへの転換
2024年10月リリースのNext.js 15では、大きな方針転換があった。
// v15: デフォルトでキャッシュされない
const data = await fetch('https://api.example.com/data')
// 明示的にキャッシュする場合
const cachedData = await fetch('https://api.example.com/data', { cache: 'force-cache' })
Route HandlersのGET関数もデフォルトでキャッシュされなくなった。これは破壊的変更だったが、「予測可能性」を取り戻すための決断だった。
v16: use cacheによる明示的制御
2025年10月のNext.js 16では、キャッシュの哲学が完全に転換した。
"use cache"
async function getData() {
const data = await fetch('https://api.example.com/data')
return data.json()
}
use cacheディレクティブにより:
- キャッシュは完全にオプトイン
- コンパイラが自動的にキャッシュキーを生成
- ページ、コンポーネント、関数単位で制御可能
「cache by default から cache by consent への転換。これは最初から持つべきだったメンタルモデルだ」
この変化は、「魔法」から「明示」への哲学転換を象徴している。
レンダリング戦略の進化
Next.jsのレンダリング戦略は段階的に進化してきた。
SSG → SSR → ISR → PPR
| 戦略 | 登場時期 | 特徴 |
|---|---|---|
| SSG | 初期 | ビルド時に全ページ生成。高速だが更新には再ビルドが必要 |
| SSR | 初期 | リクエスト毎にサーバーで生成。常に最新だがサーバー負荷が高い |
| ISR | 2020 | SSGにインクリメンタル再生成を追加。ビルド時間問題を解決 |
| PPR | 2024 | 静的シェルと動的コンテンツを1ページ内で混在 |
PPR(Partial Prerendering)の革新性
PPRはNext.js 15で実験的機能として導入され、16で本格化した。これは「ページ単位でレンダリング戦略を選ぶ」という従来の発想を覆す。
import { Suspense } from 'react'
export default function Page() {
return (
<div>
{/* 静的部分:ビルド時に生成 */}
<Header />
<Navigation />
{/* 動的部分:リクエスト時にストリーミング */}
<Suspense fallback={<Loading />}>
<DynamicContent />
</Suspense>
{/* 静的部分 */}
<Footer />
</div>
)
}
PPRの動作:
- ビルド時に静的シェル(Header、Navigation、Footer)を生成
- リクエスト時、静的シェルを即座に送信
- 動的部分(DynamicContent)はSuspense境界内でストリーミング
Vercelはこれを「Webアプリケーションのデフォルトレンダリングモデルになる」と位置づけている。
Turbopackの登場
Webpackの限界
Next.jsは長らくWebpackをバンドラーとして使用してきた。しかし、アプリケーションが大規模化するにつれて問題が顕在化した:
- コンパイル速度の低下 - 開発時のルート読み込みに時間がかかる
- 増分ビルドの非効率性 - 小さな変更でも多くのファイルを再コンパイル
- メモリ使用量の増大
Rustによる再実装
2022年、VercelはTurbopackを発表した。これはWebpackの単純なRustポートではなく、根本的に異なるアーキテクチャを持つ。
Turbo Engineの核心:
- 関数の結果を「記憶」するインメモリキャッシュ
- 変更された部分のみを再ビルド(真のインクリメンタルビルド)
- Rust言語による低レベル最適化
パフォーマンス向上:
- ローカルサーバー起動が76%高速化
- Fast Refreshが96%高速化
- 大規模アプリでWebpackの700倍高速な更新
v16でのデフォルト化
Next.js 16でTurbopackは安定版となり、デフォルトバンドラーに昇格した。開発時だけでなく本番ビルドでも使用可能になった。
これはNext.jsの歴史における重要なマイルストーンだ。Webpackへの依存を断ち切り、フレームワーク専用に最適化されたツールチェーンを持つことになった。
Next.js 16での大転換
2025年10月リリースのNext.js 16は、多くの点で「成熟」を示すバージョンとなった。
proxy.ts(middleware.tsからの移行)
middleware.tsはproxy.tsに置き換えられた。
// proxy.ts
export function proxy(request: Request) {
// ネットワーク境界を明示的に制御
}
この変更の意図は「ネットワーク境界を明示的にする」こと。middlewareという曖昧な名前ではなく、その役割(プロキシ)を正確に表す命名になった。
DevTools MCP統合
Next.js 16はMCP(Model Context Protocol)を統合した。これにより、AIエージェントが開発ワークフローに直接参加できるようになった。
- アプリケーションのコンテキストを理解したAIデバッグ
- 問題の診断と修正提案
- 動作の説明
AIとの協調を前提とした開発環境という、新しい方向性を示している。
「暗黙」から「明示」への哲学転換
Next.js 16全体を通じて見られるのは、「暗黙の魔法」から「明示的な制御」への転換だ:
- キャッシュ:暗黙的 →
use cacheで明示的 - ネットワーク境界:middleware → proxy
- バンドラー:Webpack(暗黙の設定)→ Turbopack(最適化済み)
これは開発者からのフィードバックを受けた結果でもあり、フレームワークの成熟を示している。
批判と課題
Next.jsの成功の裏には批判も存在する。
Vercelロックイン問題
ISRやEdge Functions、画像最適化など、Next.jsの多くの機能はVercelプラットフォームで最も効果的に動作する。
「Incremental Static Regenerationは極端な例だ。Vercelのプラットフォームが永続化、キャッシュ無効化、再生成トリガーを独自のメカニズムで処理する。同じアプリケーションをNetlify、Cloudflare Pages、AWSにデプロイすると、この機能は完全に失われる」
これは「Framework Defined Infrastructure」という思想の裏返しでもある。フレームワークがインフラを定義するなら、そのインフラを提供するプラットフォームに依存するのは必然かもしれない。
学習曲線の急峻さ
App Router、RSC、Server Actions、キャッシュ戦略...Next.jsの概念は年々増加している。
Pages Routerの時代は「ファイルを置けば動く」というシンプルさがあった。App Routerでは「どこで何が実行されるか」を常に意識する必要がある。これは強力だが、学習コストも高い。
頻繁な破壊的変更
メジャーバージョンごとにAPIが変わり、マイグレーションが必要になる。v14からv15でのキャッシュデフォルト変更は、多くのアプリケーションに影響を与えた。
ただし、Next.jsはcodemods(自動マイグレーションツール)を提供しており、アップグレードの負担を軽減しようとしている。
まとめ - どこへ向かうのか
Next.jsの10年を振り返ると、一貫した思想と柔軟な適応の両方が見える。
一貫しているもの
- 開発者体験の重視 - Zero Configから始まり、今もDXを最優先
- サーバーファースト - Rauchの「サーバーに近いところで計算する」思想
- Progressive Disclosure - 簡単に始められ、必要に応じて深掘りできる
変化してきたもの
- 暗黙から明示へ - 魔法のような自動化から、開発者が制御する設計へ
- ページからコンポーネントへ - ルート単位からコンポーネント単位の最適化へ
- 単独からエコシステムへ - ReactチームとMCPの統合
今後の方向性
Vercelが示す未来像は:
- PPRのデフォルト化 - すべてのページが静的と動的のハイブリッドに
- AI統合の深化 - MCPを通じたAIエージェントとの協調開発
- エッジコンピューティングの拡大 - よりユーザーに近い場所での実行
Next.jsは単なるフレームワークを超え、「Webアプリケーションはこうあるべき」という思想を体現する存在になりつつある。その思想に賛同するかどうかは開発者次第だが、その影響力は無視できない。
関連
抽出された概念
- Zero Configuration - 設定なしで動く設計原則とConvention over Configurationの思想
- Framework Defined Infrastructure - フレームワークのコードがインフラを定義するアーキテクチャパターン
- Opt-in設計とOpt-out設計 - キャッシュのデフォルト変更を通じた暗黙から明示への転換(既存ノートと対応)
- 段階的開示 - Zero ConfigとProgressive Disclosure of Complexityの関係(既存ノートと対応)
- PPR - 静的と動的を同一ページで混在させるレンダリング戦略(既存ノートと対応)