ブログに戻る
木曜日, 2023年5月4日
投稿者: Sebastian Markbåge (@sebmarkbage), Tim Neutkens (@timneutkens)
概要
Next.js 13.4 は基盤となるリリースであり、App Router の安定化を示します。主な内容は次のとおりです。
- App Router(Stable):
- React Server Components
- ネストされたルートとレイアウト(簡素化)
- データフェッチの簡素化
- ストリーミングと Suspense
- 組み込みの SEO サポート
- Turbopack(Beta): ローカル開発サーバの高速化と安定性向上
- Server Actions(Alpha): クライアント側の JavaScript を増やさずにサーバでデータを変更
Next.js 13 の最初のリリースから半年の間、私たちは将来の Next.js—App Router—の基盤構築に注力してきました。13.4 のリリースにより、本番環境で App Router を採用し始めることができます。
ターミナル
npm i next@latest react@latest react-dom@latest eslint-config-next@latest
Next.js App Router
Next.js は 2016 年に React アプリケーションのサーバレンダリングを簡単にするためにリリースされました。より動的でパーソナライズされ、グローバルなウェブを作ることが目標です。最初の発表記事では、Next.js のいくつかの設計原則を共有しました:
- ゼロセットアップ
- ファイルシステムを API として使う
- JavaScript のみ。すべては関数
- 自動サーバレンダリングとコード分割
- データフェッチは開発者次第
Next.js は今や6年を迎え、当初の設計原則は変わっていません。採用が進む中で、これらの原則をより良く達成するためのフレームワークの基盤アップグレードに取り組んできました。次世代の Next.js を開発し、13.4 でこの次世代が安定し、採用可能になりました。本稿では App Router の設計判断と選択について説明します。
ゼロセットアップ。ファイルシステムを API として使う
ファイルシステムベースのルーティングは Next.js のコア機能です。最初の投稿では、単一の React コンポーネントからルートを作る例を示しました:
pages/about.js
import React from 'react';
export default () => <h1>About us</h1>;
追加設定は不要でした。pages/ にファイルを置けば Next.js のルーターが処理してくれます。このルーティングの単純さは今も気に入っています。しかしフレームワークの利用が増えるにつれ、開発者が構築したいインターフェースの種類も増えました。レイアウト定義の改善、UI をレイアウトとしてネストする機能、読み込みやエラー状態の定義の柔軟性が求められました。
既存の Next.js ルーターにこれらを後付けするのは容易ではありませんでした。フレームワークのあらゆる部分はルーターを中心に設計される必要があります。ページ遷移、データフェッチ、キャッシュ、データの変更と再検証、ストリーミング、スタイリングなどです。ルーターをストリーミングに対応させ、レイアウトのサポートを強化するために、新しいルーターを構築することにしました。Layouts RFC の初期リリースのあと、私たちが到達したのは次のような設計です。
app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
app/page.js
export default function Page() {
return <h1>Hello, Next.js!</h1>;
}
ここで重要なのは、このコードで見えるものよりも見えない基盤です。この新しいルーター(app/ ディレクトリ経由で段階的に導入可能)は、React Server Components と Suspense を基盤にした全く異なるアーキテクチャです。この基盤により、もともと React のプリミティブを拡張するために開発された Next.js 固有の API を除去できました。たとえば、グローバルな共有レイアウトをカスタマイズするためにカスタムの _app ファイルを使う必要はなくなります。
pages/_app.js
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
Pages Router ではレイアウトの合成ができず、データフェッチをコンポーネントに共置できませんでした。App Router ではこれがサポートされます。
app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
app/dashboard/layout.js
export default function DashboardLayout({ children }) {
return (
<section>
<h1>Dashboard</h1>
{children}
</section>
);
}
Pages Router では、サーバからの初期ペイロードをカスタマイズするために _document を使っていました。
pages/_document.js
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
App Router では、Next.js から <Html>, <Head>, <Body> をインポートする必要はなく、代わりに普通の React を使います。
app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
ファイルシステムベースの新しいルーターを構築する機会は、ルーティングに関連する多くの機能要求に応える適切なタイミングでもありました。例えば:
- これまで外部 npm パッケージ(コンポーネントライブラリなど)からのグローバルスタイルシートは _app.js からのみインポート可能でした。これは開発者体験として理想的ではありませんでした。App Router では任意のコンポーネント内で任意の CSS ファイルをインポート(および共置)できます。
- getServerSideProps を使ってサーバサイドレンダリングに切り替えると、ページ全体がハイドレートされるまでアプリの操作がブロックされていました。App Router ではアーキテクチャを再設計し、React Suspense と深く統合したため、ページの一部を選択的にハイドレートし、UI の他のコンポーネントのインタラクティブ性をブロックしません。コンテンツはサーバから即座にストリーミングされ、ページの体感読み込み性能が向上します。
ルーターは Next.js を機能させる中核ですが、重要なのはルーター自体ではなく、データフェッチのようなフレームワークの残りの要素とどう統合するかです。
JavaScript のみ。すべては関数
Next.js と React の開発者は JavaScript/TypeScript でコードを書き、アプリコンポーネントを構成したいと考えています。元の投稿からの例:
pages/index.js
import React from 'react';
import Head from 'next/head';
export default () => (
<div>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<h1>Hi. I'm mobile-ready!</h1>
</div>
);
将来のバージョンでは、React の自動インポートなどの DX 改善を加えました。このコンポーネントは再利用可能なロジックをカプセル化し、ファイルシステムルーティングと組み合わせることで、JavaScript と HTML を書くような感覚で React アプリを始められる手軽さを提供します。
たとえばデータをフェッチしたい場合、Next.js の初期は次のようでした:
pages/index.js
import React from 'react';
import 'isomorphic-fetch';
export default class extends React.Component {
static async getInitialProps() {
const res = await fetch('https://api.company.com/user/123');
const data = await res.json();
return { username: data.profile.username };
}
}
その後、fetch をポリフィルしてサーバとクライアントの両方で Web fetch API を使えるようにする DX 改善を行いました。フレームワークが成熟するにつれて、データフェッチの新しいパターンを模索しました。getInitialProps はサーバとクライアントの両方で実行され、React コンポーネントを拡張して Promise を作り、その結果をコンポーネントの props に渡す API でした。getInitialProps は現在でも動作しますが、顧客フィードバックに基づいて次世代のデータフェッチ API(getServerSideProps と getStaticProps)へと進化しました:
pages/index.js
export async function getStaticProps(context) {
return { props: {} };
}
export async function getServerSideProps(context) {
return { props: {} };
}
これらの API によりコードがどこで実行されるか(クライアントかサーバか)が明確になり、Next.js アプリは自動的に静的最適化されるようになりました。さらに static export を可能にし、サーバをサポートしない環境(例: AWS S3 バケット)へのデプロイも可能にしました。
しかし、これは "ただの JavaScript" ではありませんでした。私たちは当初の設計原則により近づきたいと考えました。Next.js が作られて以来、React コアチームと密に協力し、React のプリミティブの上にフレームワーク機能を構築してきました。この協力と React コアチームの研究開発の蓄積により、Server Components を含む最新の React アーキテクチャを通じて Next.js が目標を達成する機会が生まれました。
App Router では、親しみのある async/await 構文でデータをフェッチできます。学ぶべき新しい API はありません。デフォルトで全てのコンポーネントは React Server Components であるため、データフェッチはサーバ上で安全に行われます。例えば:
app/page.js
export default async function Page() {
const res = await fetch('https://api.example.com/...');
const data = res.json();
return '...';
}
重要なのは「データフェッチは開発者次第」という原則が実現されていることです。データをフェッチして任意のコンポーネントを組み合わせられます。また、Server Components エコシステム内のサードパーティコンポーネント(例: Twitter embed の react-tweet)のように、サーバ上で完全に動作するよう設計されたコンポーネントも利用できます。
app/page.js
import { Tweet } from 'react-tweet';
export default async function Page() {
return <Tweet id="790942692909916160" />;
}
ルーターが React Suspense と統合されているため、コンテンツの一部が読み込まれている間にフォールバックをより流動的に表示し、必要に応じて段階的にコンテンツを表示できます。
app/page.js
import { Suspense } from 'react';
import { PostFeed, Weather } from './components';
export default function Page() {
return (
<section>
<Suspense fallback={<p>Loading feed...</p>}>
<PostFeed />
</Suspense>
<Suspense fallback={<p>Loading weather...</p>}>
<Weather />
</Suspense>
</section>
);
}
さらに、ルーターはページ遷移を transition としてマークし、ルート遷移を中断可能にします。
自動サーバレンダリングとコード分割
Next.js を作った当時、開発者はまだ手作業で webpack や babel などの設定を行い、React アプリを動かしていることが一般的でした。サーバレンダリングやコード分割のような最適化を手動で行うのは難しく、Next.js や他のフレームワークはこれらのベストプラクティスを強制する抽象化レイヤーを作りました。ルートベースのコード分割により、pages/ ディレクトリ内の各ファイルは...