コンテンツへスキップ
ブログに戻る

2023年5月4日 木曜日

Next.js 13.4

投稿者

Next.js 13.4は、App Routerの安定性を示す基盤となるリリースです。

  • App Router (安定版):
    • React Server Components
    • ネストされたルーティングとレイアウト
    • 簡素化されたデータ取得
    • ストリーミングとSuspense
    • 組み込みのSEOサポート
  • Turbopack (ベータ版): より高速で安定性が向上したローカル開発サーバー
  • Server Actions (アルファ版): クライアント側の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

私たちは2016年にNext.jsをリリースしました。その目的は、Reactアプリケーションをサーバーレンダリングする簡単な方法を提供し、より動的でパーソナライズされたグローバルなWebを創造することでした。

元の発表記事で、私たちはNext.jsのデザイン原則をいくつか共有しました。

  • ゼロセットアップ。ファイルシステムをAPIとして使用
  • JavaScriptのみ。すべては関数
  • 自動サーバーレンダリングとコード分割
  • データ取得は開発者次第

Next.jsは現在6周年を迎えました。私たちの元々の設計原則は変わらず、Next.jsがより多くの開発者や企業に採用されるにつれて、これらの原則をより良く達成するために、フレームワークの基盤的なアップグレードに取り組んできました。

私たちはNext.jsの次世代の開発に取り組んでおり、本日13.4のリリースにより、この次世代は安定し、採用する準備が整いました。この投稿では、App Routerに関する私たちの設計上の決定と選択についてさらに詳しく説明します。

ゼロセットアップ。ファイルシステムをAPIとして使用

ファイルシステムベースのルーティングは、Next.jsの核となる機能です。元の投稿で、私たちは単一のReactコンポーネントからルートを作成するこの例を示しました。

pages/about.js
// Pages Router
 
import React from 'react';
export default () => <h1>About us</h1>;

追加の設定は何もありませんでした。`pages/`内にファイルを置くだけで、Next.jsルーターが残りの処理を行ってくれました。私たちはこのルーティングのシンプルさを今でも気に入っています。しかし、フレームワークの使用が拡大するにつれて、開発者が構築しようとしているインターフェースの種類も増えてきました。

開発者からは、レイアウトの定義、UIの一部をレイアウトとしてネストすること、およびローディング状態とエラー状態の定義に対するより柔軟なサポートの改善が求められていました。これらを既存のNext.jsルーターに後付けすることは容易ではありませんでした。

フレームワークのすべての部分は、ルーターを中心に設計される必要があります。ページ遷移、データ取得、キャッシング、データの変更と再検証、ストリーミング、コンテンツのスタイル設定などです。

私たちのルーターをストリーミングと互換性を持たせ、レイアウトの強化されたサポートに関するこれらの要求を解決するために、私たちはルーターの新しいバージョンの構築に着手しました。

これは、Layouts RFCの最初のリリース後に私たちが到達した地点です。

app/layout.js
// New: App Router ✨
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 ComponentsSuspenseの基盤の上に構築された、全く異なるアーキテクチャを持っています。

この基盤により、Reactプリミティブを拡張するために当初開発されたNext.js固有のAPIを削除することができました。たとえば、グローバルな共有レイアウトをカスタマイズするために、カスタムの`_app`ファイルを使用する必要はなくなりました。

pages/_app.js
// Pages Router
 
// This "global layout" wraps all routes. There's no way to
// compose other layout components, and you cannot fetch global
// data from this file.
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

Pages Routerでは、レイアウトを構成することができず、データ取得もコンポーネントと一緒に配置できませんでした。新しいApp Routerでは、これがサポートされています。

app/layout.js
// New: App Router ✨
// The root layout is shared for the entire application
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
app/dashboard/layout.js
// Layouts can be nested and composed
export default function DashboardLayout({ children }) {
  return (
    <section>
      <h1>Dashboard</h1>
      {children}
    </section>
  );
}

Pages Routerでは、`_document`はサーバーからの初期ペイロードをカスタマイズするために使用されていました。

pages/_document.js
// Pages Router
 
// This file allows you to customize the <html> and <body> tags
// for the server request, but adds framework-specific features
// rather than writing HTML elements.
import { Html, Head, Main, NextScript } from 'next/document';
 
export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

App Routerでは、Next.jsから``、``、``をインポートする必要はなくなりました。代わりに、Reactをそのまま使用します。

app/layout.js
// New: App Router ✨
// The root layout is shared for the entire application
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

新しいファイルシステムルーターを構築する機会は、ルーティングシステムに関する他の多くの機能要求に対処する適切な時期でもありました。例えば

  • 以前は、グローバルなスタイルシートを外部のnpmパッケージ(コンポーネントライブラリなど)から`_app.js`でのみインポートできました。これは理想的とは言えない開発体験でした。App Routerでは、どのコンポーネントでも任意のCSSファイルをインポート(および併置)できます。
  • 以前、Next.jsでサーバーサイドレンダリング(`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>
);

Next.jsの将来のバージョンでは、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 };
  }
}

Next.jsの将来のバージョンでは、`fetch`をポリフィルするDX(開発者体験)の改善を追加しました。これにより、`isomorphic-fetch`や`node-fetch`をインポートする必要がなくなり、Webの`fetch API`をクライアントとサーバーの両方で使用できるようになりました。

採用が進みフレームワークが成熟するにつれて、私たちはデータ取得の新しいパターンを模索しました。

`getInitialProps`はサーバーとクライアントの両方で実行されました。このAPIはReactコンポーネントを拡張し、Promiseを作成してその結果をコンポーネントの`props`に転送できるようにしました。

今日でも`getInitialProps`は機能しますが、お客様のフィードバックに基づいて、次の世代のデータ取得APIである`getServerSideProps`と`getStaticProps`へと改良を進めました。

pages/index.js
// Generate a static version of the route
export async function getStaticProps(context) {
  return { props: {} };
}
// Or dynamically server-render the route
export async function getServerSideProps(context) {
  return { props: {} };
}

これらのAPIは、コードがクライアントとサーバーのどちらで実行されているかをより明確にし、Next.jsアプリケーションが自動的に静的に最適化されることを可能にしました。さらに、静的エクスポートを可能にし、Next.jsをサーバーをサポートしない場所(例:AWS S3バケット)にデプロイできるようにしました。

しかし、これは「ただのJavaScript」ではありませんでした。私たちは元の設計原則により忠実に従いたいと考えていました。

Next.jsが作成されて以来、私たちはMetaのReactコアチームと緊密に協力し、Reactプリミティブの上にフレームワーク機能を構築してきました。このパートナーシップは、Reactコアチームによる長年の研究開発と相まって、Next.jsがServer Componentsを含む最新バージョンのReactアーキテクチャを通じて目標を達成する機会をもたらしました。

App Routerでは、おなじみの`async`および`await`構文を使用してデータを取得します。新しく学習するAPIはありません。デフォルトでは、すべてのコンポーネントはReact Server Componentsであるため、データ取得はサーバー上で安全に行われます。例えば

app/page.js
export default async function Page() {
  const res = await fetch('https://api.example.com/...');
  // The return value is *not* serialized
  // You can use Date, Map, Set, etc.
  const data = res.json();
 
  return '...';
}

重要なことに、「データ取得は開発者次第」という原則が実現されました。データを取得し、任意のコンポーネントを構成できます。ファーストパーティのコンポーネントだけでなく、Server Componentsエコシステムの任意のコンポーネント、例えばServer Componentsと統合され、完全にサーバー上で実行されるように設計されたTwitterの埋め込み`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>
  );
}

さらに、ルーターはページ遷移を「トランジション」としてマークし、ルート遷移を中断可能にします。

自動サーバーレンダリングとコード分割

Next.jsを作成した当時、開発者がReactアプリケーションを動作させるために、webpack、babel、その他のツールを手動で設定するのが一般的でした。サーバーレンダリングやコード分割のようなさらなる最適化は、カスタムソリューションでは実装されていないことがよくありました。Next.jsは、他のReactフレームワークと同様に、これらのベストプラクティスを実装し、強制するための抽象化レイヤーを作成しました。

ルートベースのコード分割とは、`pages/`ディレクトリ内の各ファイルが独自のJavaScriptバンドルにコード分割されることを意味し、ファイルシステムの削減と初期ページ読み込みパフォーマンスの向上に役立ちました。

これは、Next.jsを使用したサーバーレンダリングアプリケーションとシングルページアプリケーションの両方に有益でした。後者はアプリケーション起動時に単一の大きなJavaScriptバンドルを読み込むことが多かったためです。しかし、コンポーネントレベルのコード分割を実装するには、開発者は`next/dynamic`を使用してコンポーネントを動的にインポートする必要がありました。

app/page.tsx
import dynamic from 'next/dynamic';
 
const DynamicHeader = dynamic(() => import('../components/header'), {
  loading: () => <p>Loading...</p>,
});
 
export default function Home() {
  return <DynamicHeader />;
}

App Routerでは、Server Componentsはブラウザ用のJavaScriptバンドルに含まれません。クライアントコンポーネントはデフォルトで自動的にコード分割されます(Next.jsではwebpackまたはTurbopackを使用)。さらに、ルーターアーキテクチャ全体がストリーミングとSuspenseに対応しているため、UIの一部をサーバーからクライアントへ段階的に送信できます。

例えば、条件ロジックでコードパス全体をコード分割できます。この例では、ログインしていないユーザーのためにダッシュボードのクライアントサイドJavaScriptを読み込む必要はありません。

app/layout.tsx
import { getUser } from './auth';
import { Dashboard, Landing } from './components';
 
export default async function Layout() {
  const isLoggedIn = await getUser();
  return isLoggedIn ? <Dashboard /> : <Landing />;
}

Turbopack (ベータ版)

Turbopackは、Next.jsを通じてテストおよび安定化を進めている新しいバンドラーで、Next.jsアプリケーションの開発中(`next dev --turbo`を使用)のローカルでの反復作業を高速化し、まもなく本番ビルド(`next build --turbo`)も高速化します。

Next.js 13でのアルファ版リリース以来、バグの修正や不足している機能のサポートに取り組んできた結果、採用が着実に増えていることを確認しています。私たちはVercel.comでTurbopackをドッグフーディングし、多くのVercelのお客様が大規模なNext.jsウェブサイトを運用している中でフィードバックを収集し、安定性の向上に努めてきました。テストとバグ報告にご協力いただいたコミュニティの皆様に感謝いたします。

そして6ヶ月後の今、ベータフェーズへと移行する準備が整いました。

TurbopackはまだwebpackとNext.jsとの完全な機能パリティには達していません。これらの機能のサポートはこのissueで追跡しています。しかし、ほとんどのユースケースは現在サポートされているはずです。このベータ版の目標は、採用の増加に伴う残りのバグに対処し続け、将来のバージョンでの安定性に向けて準備することです。

Turbopackのインクリメンタルエンジンとキャッシュレイヤーの改善への投資は、ローカル開発を高速化するだけでなく、間もなく本番ビルドも高速化します。将来のNext.jsバージョンで`next build --turbo`を実行して瞬時にビルドできるようになるのをお楽しみに。

Next.js 13.4で`next dev --turbo`を使用して、本日Turbopackベータ版をお試しください。

Server Actions (アルファ版)

Reactエコシステムでは、フォーム、フォームの状態管理、データのキャッシュと再検証に関する多くのイノベーションとアイデアの探求が行われてきました。時間の経過とともに、Reactはこれらのパターンの一部についてより意見を持つようになりました。例えば、フォームの状態には「アンコントロールドコンポーネント」が推奨されています。

現在のソリューションエコシステムは、再利用可能なクライアントサイドソリューションか、フレームワークに組み込まれたプリミティブかのいずれかでした。これまで、サーバーミューテーションとデータプリミティブを構成する方法はありませんでした。Reactチームは、ミューテーションのファーストパーティソリューションに取り組んできました。

Next.jsで実験的なServer Actionsのサポートを発表できることを嬉しく思います。これにより、中間APIレイヤーを作成することなく、サーバー上でデータを変更し、関数を直接呼び出すことができます。

app/post/[id]/page.tsx (Server Component)
import kv from './kv';
 
export default function Page({ params }) {
  async function increment() {
    'use server';
    await kv.incr(`post:id:${params.id}`);
  }
 
  return (
    <form action={increment}>
      <button type="submit">Like</button>
    </form>
  );
}

Server Actionsを使用すると、強力なサーバーファーストのデータ変更、少ないクライアントサイドJavaScript、そしてプログレッシブエンハンスされたフォームを実現できます。

app/post/new/page.tsx (Server Component)
import db from './db';
import { redirect } from 'next/navigation';
 
async function create(formData: FormData) {
  'use server';
  const post = await db.post.insert({
    title: formData.get('title'),
    content: formData.get('content'),
  });
  redirect(`/blog/${post.slug}`);
}
 
export default function Page() {
  return (
    <form action={create}>
      <input type="text" name="title" />
      <textarea name="content" />
      <button type="submit">Submit</button>
    </form>
  );
}

Next.jsのServer Actionsは、Next.jsキャッシュ、Incremental Static Regeneration (ISR)、クライアントルーターを含む、残りのデータライフサイクルとの深い統合のために設計されています。

新しいAPIである`revalidatePath`と`revalidateTag`によるデータの再検証は、データの変更、ページの再レンダリング、またはリダイレクトが**1回のネットワーク往復**で発生できることを意味し、アップストリームプロバイダーが遅い場合でも、クライアントに正しいデータが表示されることを保証します。

app/dashboard/posts/page.tsx (Server Component)
import db from './db';
import { revalidateTag } from 'next/cache';
 
async function update(formData: FormData) {
  'use server';
  await db.post.update({
    title: formData.get('title'),
  });
  revalidateTag('posts');
}
 
export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['posts'] } });
  const data = await res.json();
  // ...
}

Server Actionsは構成可能に設計されています。Reactコミュニティの誰もがServer Actionsを構築、公開し、エコシステムで配布できます。Server Componentsと同様に、クライアントとサーバーの両方で構成可能なプリミティブの新しい時代に私たちは興奮しています。

Server Actionsは、本日Next.js 13.4でアルファ版として利用可能です。Server Actionsの使用をオプトインすることで、Next.jsはReactの実験的なリリースチャネルを使用します。

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverActions: true,
  },
};
 
module.exports = nextConfig;

その他の改善点

  • ドラフトモード: ヘッドレスCMSからドラフトコンテンツを取得し、レンダリングします。ドラフトモードは`pages`と`app`の両方で機能します。既存のプレビューモードAPIを強化および簡素化しましたが、これは`pages`では引き続き機能します。プレビューモードは`app`では機能しません。ドラフトモードを使用してください。

よくある質問

App Routerの安定性とはどういう意味ですか?

App Routerを本日安定版とマークしたからといって、私たちの作業が完了したわけではありません。安定性とは、App Routerの核となる部分が本番環境に対応しており、私たち自身の内部テストだけでなく、多くのNext.jsアーリーアダプターによって検証されていることを意味します。

Server Actionsが完全に安定することを含め、今後さらに最適化を行いたいと考えています。コミュニティが今日からアプリケーションの学習と構築を開始すべき場所について明確さを持てるよう、コアの安定性を推進することは私たちにとって重要でした。

App RouterはReactの`canary`チャネル上に構築されており、これは現在、Server Componentsのような機能のフレームワーク採用の準備ができています。詳細はこちら

これはNext.jsのベータ版ドキュメントにとって何を意味しますか?

本日より、新しいアプリケーションはApp Routerで構築することをお勧めします。App Routerを説明するためにゼロから書き直されたNext.jsベータ版ドキュメントは、安定版のNext.jsドキュメントに統合されました。App RouterとPages Routerを簡単に切り替えることができるようになりました。

App Routerの採用方法を学ぶために、App Router段階的導入ガイドを読むことをお勧めします。

Pages Routerはなくなるのですか?

いいえ。私たちは、バグ修正、改善、セキュリティパッチを含め、将来の複数のメジャーバージョンにわたって`pages/`開発をサポートすることにコミットしています。開発者が準備ができたときにApp Routerを段階的に採用するための十分な時間を確保したいと考えています。

`pages/`と`app/`の両方を本番環境で併用することはサポートされており、推奨されています。App Routerはルートごとに採用できます。

これはServer Componentsが「完成」したことを意味しますか?

Next.jsは、Server Componentsを含むReactアーキテクチャ上に構築することを選択しているフレームワークの一つです。App Routerで提供される体験が、他のフレームワーク(または新しいフレームワーク)にもこのアーキテクチャの使用を検討するよう促すことを願っています。

このエコシステムには、無限スクロールの処理など、まだ定義されていないパターンがあります。今のところ、エコシステムが成長し、ライブラリが作成または更新されるまでの間は、これらのパターンにはクライアントサイドのソリューションを使用することをお勧めします。

コミュニティ

Next.jsは、2,600人以上の個人開発者、GoogleやMetaのような業界パートナー、そしてVercelのコアチームの共同作業の結果です。GitHub DiscussionsRedditDiscordでコミュニティに参加してください。

このリリースは以下によって提供されました

および以下の方々の貢献: @shuding, @huozhi, @wyattfry, @styfle, @sreetamdas, @afonsojramos, @timneutkens, @alexkirsz, @chriswdmr, @jankaifer, @pn-code, @kdy1, @sokra, @kwonoj, @martin-wahlberg, @Kikobeats, @JTaylor0196, @sebmarkbage, @ijjk, @gnoff, @jridgewell, @sagarpreet-xflowpay, @balazsorban44, @cprussin, @ForsakenHarmony, @li-jia-nan, @dciug, @albertothedev, @DuCanhGH, @feedthejim, @patrick91, @padmaia, @sophiebits, @eps1lon, @reconbot, @acdlite, @cjmling, @nabsul, @motopods, @hanneslund, @tunamagur0, @devknoll, @apeltop, @maranomynet, @y-tsubuku, @EndangeredMassa, @ykzts, @AviAvinav, @adilansari, @wyattjoh, @charkour, @delbaoliveira, @agadzik, @Just-Moh-it, @rodrigofeijao, @leerob, @juliusmarminge, @koba04, @Phiction, @jessewarren-aa, @ryo-manba, @Yovach, and @dylanjha。