* Blogs are only available in Japanese. Sorry for the inconvenience.

どうも、KODANSHAtech フロントエンドエンジニアの榎山です。

先日、Next.js の App Router を試してみたので、記事執筆まで時間がかかりましたが、そのときにつまづいてしまったポイントと、メリットと感じたポイントを簡単に紹介したいと思います。

はじめに

Next.js の新しいルーティングシステムである App Router が、2023年5月にリリースされた Next.js 13.4 で stable になりました

KODANSHAtech では Next.js を多数のプロジェクトで利用しています。メジャーなサービスでは、まだ App Router を本番採用しているものはないですが、新規のプロジェクトで積極的に採用を開始していたり、理解を深めるために研究を行っています。

筆者も今研究を進めている中で、「つまづいたポイント」と「メリットと感じたポイント」があったので、このブログに記録しておこうと思います。

つまづいたポイント

実装を進めている中で、開発サーバーを起動してページにアクセスするとエラーが発生する問題が発生しました。

その時出ていたエラーというのが以下です。

error TypeError: ComponentMod.renderToReadableStream is not a function
at AsyncLocalStorage.run (node:async_hooks:338:14)

エラーの内容を読んでも問題点が特定できず困っていたところ、同一のルートセグメントに route.tspage.tsx の両方を設置していたことに気づきました。

「同一のルートセグメント」というのは、同じルーティングが行われるセグメントを意味しており、Next.js App Router の仕組みでは、それはつまり app 配下の 「同じディレクトリ」のことになります。

つまり、具体的には、ルートハンドラーのapp/blogs/route.tsapp/blogs/page.tsx と同じディレクトリに設置していたことが原因でした。

このことは、以下のように公式ドキュメントにも記載されており、ルーティングの仕組みを考えると納得の原因なのですが、エラーメッセージから判断しにくくしばらくハマってしまいました。

Route Handlers can be nested inside the app directory, similar to page.js and layout.js. But there cannot be a route.js file at the same route segment level as page.js.

https://nextjs.org/docs/app/building-your-application/routing/router-handlers

ルートハンドラーを app/api/blogs/route.ts のように別のルートセグメントに移動することでエラーを解消できました。

メリットと感じたポイント

App Router を使うメリットとして、Server Components などの新しい React 技術を使うことで、ページロードの高速化といったサイトパフォーマンスに優れた Web サイト構築が可能なことが紹介されています。

ここでは、開発者の立場から、以前の Pages Router と比較して特にメリットを実感した点を紹介します。

レイアウトのネスト

App Router では、レイアウトの機能が強化され、規約にしたがってレイアウトファイルを設定することで、特定のページやそのグループごとにネストされたレイアウトを適用することが可能になっています。

https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts

その規約というのは、レイアウトを適用したいディレクトリに layout.tsx ファイルを設置するというものになります。

以下が実際に試した例です。

src/app/layout.ts

このファイルは、すべてのページに対して適用されるルートのレイアウトになります。

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja">
      <body>{children}</body>
    </html>
  );
}

src/app/blogs/layout.tsx

このファイルは、blogs ディレクトリにのみ適用されるレイアウトになります。

export default function BlogLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return <main className="mx-2 sm:mx-4 relative">{children}</main>;
}

このとき、レイアウトはディレクトの階層にしたがって、親子関係に「ネスト」された形になります。つまり、src/app/blogs/layout.tsx は子レイアウトとして、親レイアウトである src/app/layout.tsx にラップされて表示されます。

このレイアウトのネストの機能は、Next.js のデファイルシステムベースのルーティングシステムと非常によく噛み合っていると感じていて、ページのレイアウトとそれを適用したいページのコンポーネントをまとめて管理できるため、雑多になりがちなコンポーネントのディレクトリ構成を整理しやすくなると思っています。

GitHub リポジトリ

今回の研究に利用した GitHub リポジトリはこちらです。