Next.js13から登場したApp Routerという機能について解説していきます。
なぜこの技術が出てきたのか?
今までのNext.jsはデータ取得はサーバーサイド、レンダリングはクライアントサイドという形で明確に役割が分かれていました。
しかし、クライアントサイドの端末の性能はユーザーによってまちまちです。(型落ちのPCやスマホなどを利用している人も多いです。)なので、性能をチューニングしやすいサーバー側で思い切ってレンダリングも行ってしまおうというのが基本思想になります。
こうすることによりユーザー起因でページのパフォーマンスが落ちるということを回避できます。
特徴
React18のRSCとSSRを併用している。
RSC、Server Action、Experimental Hooksが利用可能になっている。
そもそもライブラリがまだ対応していない部分もある。
イメージ
SubTree(サブツリー)
app階層からそれぞれ分岐しているそれぞれの塊のことをSubTreeと呼びます。
Segment
SubTree内の各要素(aaa、bbbなど)のことです。各セグメントはURLパスでアクセスすることが可能になります。
上記例でいえば、「https://xxx.com/aaa」や、「https://xxx.com/bbb/ccc」などといった感じでアクセスすることが可能です。
appディレクトリ
appディレクトリ配下にはあらかじめ決められたファイル名が存在します。
layout | レイアウトを定義する。app直下のコンポーネントはルートレイアウトとみなされます。 従来のpage-routerでいえば「_app.tsx」と同じようにアプリケーション全体で適用したい設定はこちらにかけます。 なお、明示的に実装しなくてもpageコンポーネントは自動的にlayoutに渡されてラップされます。 なお、こちらlayoutで定義されている共通部品として定義されている内容は再度ページ読み込みが起こらない限りは再レンダリングは行われない仕様になっています。(パーシャルレンダリング) |
error | エラーバウンダリー |
loading | ローディング、これを用意した場合はページ全体がローディング対象になりますが、ページの中のフェッチ部分のみローディング対象とするというような指定も可能です。 |
page | 対象のセグメントのユーザーインターフェースを記述するコンポーネントです。 |
not-found | ページが見つからない場合のページをカスタマイズできます。 import { notFound } from 'next/navigation' notFoundをいつ出すかの制御は上記コンポーネントを実装コードにインポートしたうえで実施します。 |
上記のファイル名を配置した場合に自動的にNext.jsがラップしたコンポーネントを作成してくれるようになります。layout.tsx、error.tsx、loading.tsx、page.tsxをそれぞれ用意した場合は以下のような階層でラップされることになります。
1 2 3 4 5 6 7 |
<Layout> <ErrorBoundary fallback={<Error />}> <Suspense fallback={<Loading />}> <Page /> </Suspense> </ErrorBoundary> </Layout> |
ただし、loadingに関しては全てのコンポーネントにおいてloadingが動くわけではないという状況の方が多いです。一部フェッチしたいコンポーネントだけ適用するということが一般的なので実質的にloding.tsxを単体で使うことは開発においては稀です。
以下のように個別にフェッチしたいコンポーネント周りだけSuspenseを使ってラップする実装がメインになります。(そうするとloading.tsxというコンポーネント自体が不要になります。)
1 2 3 |
<Suspense fallback={<loading用コンポーネント />}> <フェッチコンポーネント/> </Suspense> |
Error
1 2 3 4 5 6 7 8 9 10 11 |
'use client' export default function Error({ error }: { error: Error }) { return ( <div> <p> Data fetching in server failed </p> </div> ) } |
サーバーコンポーネントで発生したエラーはクライアントの方で表示させる必要があるので、一番上に「'use client'」という構文を使うことでクライアントコンポーネントにすることを実現します。
仕組み
appディレクトリの配下のコンポーネントは全てデフォルトで「サーバーコンポーネント」になっています。
子コンポーネントを追加したい場合
appフォルダの配下に「components」というディレクトリを作成してその下に子コンポーネントを配置していくのが一般的です。
Nested-Layout
このような形でapp配下にフォルダを入れ子にして配置してlayoutやpageファイルなども配置していくとURLも入れ子になっていきます。
上記例でいえば、thirdのpageにアクセスしたい場合は「http://localhost:3000/second/third/」と指定すればアクセスすることが可能です。
dynamic segment
以下のようなディレクトリ階層を用意します。
1 2 3 4 |
users └[userId] └page.tsx └page.tsx |
[userId]フォルダの中のpage.tsxを以下のように実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import { notFound } from 'next/navigation' async function fetchUser(userId: string) { const url = `${process.env.url}/rest/v1/users?id=eq.${userId}` const res = await fetch( url,cache: 'no-store', } ) const users = await res.json() return users[0] } export default async function UserDetailPage({ params }: PageProps) { const user = await fetchUser(params.userId) if (!user) return notFound() return ( <div className="mt-16 p-8"> <p> <strong className="mr-3">Name:</strong> {user.name} </p> <p> <strong className="mr-3">Age:</strong> {user.age} </p> </div> ) } |
URLとしては、「親セグメント/{userId}」みたいな感じで上記処理がレンダリングされます。詳細ページの表示などに使えます。
詳細も完全静的ページにしたい場合
以下のように[[userId]/page.tsx]の中に「generateStaticParams」という関数を追加します。(従来のNext.jsの文法であれば、getStaticPathsに該当します。)
1 2 3 4 5 6 7 8 |
export async function generateStaticParams() { const res = await fetch(URL) const users = await res.json() return users.map((user) => ({ userId: user.id.toString(), })) } |
こうすることで、詳細ページも静的なページの生成に成功します。(データベースの値が更新されても、更新が反映されません。)
メリットとしては、静的ページにすることでページの描画速度がかなり上昇します。
ページ遷移
ソフトナビゲーション
最初にページ遷移した際にフェッチが行われるのですが、2回目以降のページ遷移ではキャッシュを利用するためフェッチが行われない仕組みのことです。キャッシュ自体はクライアントサイドで行います。
prefetchしてページ遷移するnext/linkのLinkなどで遷移した際はソフトナビゲーションが適用されます。(ただ、propsの一つである「prefetch」をfalse指定するとLinkでもハードナビゲーションにできます。)
メリット
高速にページ遷移ができる。
デメリット
仮にサーバーサイドでデータ更新が行われても事前にクライアントサイドでキャッシュされた情報が優先されるので内容が最新状態に更新されない。
ハードナビゲーション
毎回、最新の状態をフェッチすること。userRouterのpushなどで遷移してきた場合などはハードナビゲーションが適用されます。
注意点
ただし、Next.js13.4以降だとuserRrouter.pushは初回だけハードナビゲーションする仕様に変更になりました。2回目以降はソフトナビゲーションになります。13.3以前なら毎回ハードナビゲーションが動きます。
この記事へのコメントはありません。