Next.jsのアーキテクチャと思想の変遷

Next.jsは2016年の登場以来、Reactエコシステムにおける事実上の標準フレームワークへと成長した。しかしその道のりは単なる機能追加の歴史ではなく、Web開発のあるべき姿についての思想的な探求でもあった。

この記事では、Next.jsがどのような設計思想に基づいて生まれ、どのように進化してきたかを時系列で追いながら、各決定の背景にある「なぜ」を探っていく。

設計思想の原点(2016年)

Guillermo Rauchの6原則

Next.jsは2016年10月25日、Vercel(当時Zeit)の創業者Guillermo RauchによってオープンソースとしてGitHubに公開された。その設計は6つの原則に基づいていた:

  1. Zero Setup - 設定なしですぐに動く
  2. Filesystem as API - ファイルシステムがルーティングを定義する
  3. Only JavaScript - すべてが関数として表現される
  4. Automatic Code Splitting - 自動的なコード分割
  5. Configurable Data Fetching - 柔軟なデータ取得
  6. 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の仕組み:

  1. 初回リクエストでは静的に生成されたページを即座に返す
  2. revalidateで指定した時間が経過すると、次のリクエスト時にバックグラウンドで再生成
  3. 再生成完了後、新しいページがキャッシュされる

これにより「ビルドに時間がかかる大規模サイト」の問題を解決した。全ページを事前生成する必要がなくなり、必要に応じて増分的に生成できるようになった。

ただし、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は成功を収めたが、アプリケーションが複雑化するにつれて限界が見えてきた:

  1. 複雑なレイアウト管理 - ネストされたルートや共有レイアウトの管理が煩雑
  2. ページ単位のデータ取得 - コンポーネント単位での最適化ができない
  3. ルート変更時の全体再レンダリング - パフォーマンス上の課題
  4. サーバー/クライアントの曖昧な境界 - どこで何が実行されるかが不明確

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

主な変更点:

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>
}

この設計により:

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 PrimitivesTailwind 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の動作:

  1. ビルド時に静的シェル(Header、Navigation、Footer)を生成
  2. リクエスト時、静的シェルを即座に送信
  3. 動的部分(DynamicContent)はSuspense境界内でストリーミング

Vercelはこれを「Webアプリケーションのデフォルトレンダリングモデルになる」と位置づけている。

Turbopackの登場

Webpackの限界

Next.jsは長らくWebpackをバンドラーとして使用してきた。しかし、アプリケーションが大規模化するにつれて問題が顕在化した:

Rustによる再実装

2022年、VercelはTurbopackを発表した。これはWebpackの単純なRustポートではなく、根本的に異なるアーキテクチャを持つ。

Turbo Engineの核心:

パフォーマンス向上:

v16でのデフォルト化

Next.js 16でTurbopackは安定版となり、デフォルトバンドラーに昇格した。開発時だけでなく本番ビルドでも使用可能になった。

これはNext.jsの歴史における重要なマイルストーンだ。Webpackへの依存を断ち切り、フレームワーク専用に最適化されたツールチェーンを持つことになった。

Next.js 16での大転換

2025年10月リリースのNext.js 16は、多くの点で「成熟」を示すバージョンとなった。

proxy.ts(middleware.tsからの移行)

middleware.tsproxy.tsに置き換えられた。

// proxy.ts
export function proxy(request: Request) {
  // ネットワーク境界を明示的に制御
}

この変更の意図は「ネットワーク境界を明示的にする」こと。middlewareという曖昧な名前ではなく、その役割(プロキシ)を正確に表す命名になった。

DevTools MCP統合

Next.js 16はMCP(Model Context Protocol)を統合した。これにより、AIエージェントが開発ワークフローに直接参加できるようになった。

AIとの協調を前提とした開発環境という、新しい方向性を示している。

「暗黙」から「明示」への哲学転換

Next.js 16全体を通じて見られるのは、「暗黙の魔法」から「明示的な制御」への転換だ:

これは開発者からのフィードバックを受けた結果でもあり、フレームワークの成熟を示している。

批判と課題

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年を振り返ると、一貫した思想と柔軟な適応の両方が見える。

一貫しているもの

変化してきたもの

今後の方向性

Vercelが示す未来像は:

  1. PPRのデフォルト化 - すべてのページが静的と動的のハイブリッドに
  2. AI統合の深化 - MCPを通じたAIエージェントとの協調開発
  3. エッジコンピューティングの拡大 - よりユーザーに近い場所での実行

Next.jsは単なるフレームワークを超え、「Webアプリケーションはこうあるべき」という思想を体現する存在になりつつある。その思想に賛同するかどうかは開発者次第だが、その影響力は無視できない。


関連

抽出された概念