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

2023年5月4日 木曜日

Next.js 13.4

投稿者

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

  • App Router (安定版):
    • Reactサーバーコンポーネント
    • ネストされたルートとレイアウト
    • データ取得の簡略化
    • ストリーミングと Suspense
    • SEO サポートの組み込み
  • Turbopack (ベータ版): より高速で安定性の向上したローカル開発サーバー
  • Server Actions (アルファ版): クライアント JavaScript ゼロでサーバー上でデータを変更

6ヶ月前に 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 の次世代に取り組んできました。そして本日、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 から <Html><Head><Body> をインポートする必要はなくなりました。代わりに、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 のバージョンでは、isomorphic-fetchnode-fetch をインポートする必要がなく、クライアントとサーバーの両方で Web fetch API を使用できるように、fetch をポリフィルする DX 改善を追加しました。

採用が増え、フレームワークが成熟するにつれて、データ取得のための新しいパターンを探求しました。

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

getInitialProps は現在でも機能しますが、顧客からのフィードバックに基づいて、次世代のデータ取得 API である getServerSidePropsgetStaticProps に進化しました。

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 アプリケーションを自動的に静的に最適化できるようになりました。さらに、静的エクスポートを可能にし、サーバーをサポートしない場所(例: AWS S3 バケット)に Next.js をデプロイできるようになりました。

しかし、これは「JavaScript のみ」ではなく、当初の設計原則にさらに近づきたいと考えていました。

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

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

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 '...';
}

決定的に、「データ取得は開発者に委ねられる」という原則が実現されています。データを取得し、任意のコンポーネントを構成できます。そして、ファーストパーティのコンポーネントだけでなく、Twitter embedreact-tweet のような Server Component と統合するように設計され、完全にサーバーで実行される 任意の コンポーネントも利用できます。

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 を作成した当時、開発者が webpack、babel、その他のツールを手動で構成して React アプリケーションを実行するのが一般的でした。サーバーレンダリングやコード分割などの追加の最適化は、カスタムソリューションで実装されないことがよくありました。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 Component はブラウザ用の JavaScript バンドルに含まれません。Client Component はデフォルトで自動的にコード分割されます(webpack または Next.js の 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 の完全な機能パリティをまだ持っていません。これらの機能のサポートはこの問題で追跡しています。しかし、ほとんどのユースケースは現在サポートされています。このベータ版の目標は、採用の増加による残りのバグに対処し続け、将来のバージョンでの安定化を準備することです。

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

next dev --turbo で、Next.js 13.4 の 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 Cache、Incremental Static Regeneration (ISR)、クライアントルーターを含む、データライフサイクルの他の部分との深い統合のために設計されています。

revalidatePath および revalidateTag という新しい API によるデータ再検証は、変更、ページの再レンダリング、またはリダイレクトが 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;

その他の改善点

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

よくある質問

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 または Pages Router を簡単に切り替えることができます。

App Router の段階的採用方法を学ぶには、App Router Incremental Adoption Guide を読むことをお勧めします。

Pages Router は廃止されますか?

いいえ。pages/ 開発のサポート、バグ修正、改善、セキュリティパッチを、今後複数メジャーバージョンにわたって提供することをお約束します。開発者が準備ができたときに、App Router を段階的に採用するのに十分な時間を提供したいと考えています。

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

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

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

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

コミュニティ

Next.js は、2,600 人を超える個々の開発者、Google や Meta のような業界パートナー、そして Vercel のコアチームの協力の成果です。コミュニティに GitHub DiscussionsReddit、および Discord に参加してください。

このリリースは、以下の方々によってもたらされました。

そして、@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、および @dylanjha の貢献。