ざっくりApp Router入門【Next.js】
App Routerについて色々調べたので、「Page Routerからどう変わったか?」を中心にいろいろ書きます。
※Next.jsのバージョンは13.4です
🤗Page Routerから変わった点
「これだけ抑えておけばOK」な内容についてザックリ書きます。
特別な意味を持つファイルができた
Page Routerではpages
直下につくったファイルがすべてルーティングされました。
ですがApp Routerでは、app
直下のpage.tsx
と書かれたファイルだけがルーティングの対象になります。
ほかにも、layout.tsx
と書かれたファイルを置いておくと「layout.tsx
でpage.tsx
を自動的にラップする」ができたりします👇
📁app
└📝layout.tsx
└📝page.tsx
よく使うのはこの2つだと思いますが、他にも特別な意味を持つファイルがたくさん用意されています👇
すべてサーバーコンポーネントになった
App Routerでは、すべてのコンポーネントがサーバーコンポーネントになりました。
ファイルの先頭に"use client"
と書くことで、クライアントコンポーネントにすることもできます。
たとえば、以下のように書くとデフォルトでサーバーコンポーネントになりますが👇
export default function Home() {
return <main>home</main>;
}
以下のようにファイルの先頭に"use client"
と書くとクライアントコンポーネントになります👇
"use client"
export default function Home() {
return <main>home</main>;
}
- サーバーコンポーネント:
「先にサーバー側でレンダリングしてからクライアントに送ろう!」なコンポーネントのこと - クライアントコンポーネント(従来のコンポーネント):
「クライアント側でレンダリングしよう!」なコンポーネントのこと
この2つの違いが分からない人は、こちらの記事をオススメします👇
一言で理解するReact Server Components
この概念が分からないままApp Routerを使うと99%ハマると思うので、先に理解することをオススメします!
特別な意味を持つディレクトリが増えた
4つくらい増えました。
1. 論理グループ
ディレクトリ名を()
で囲うと、ルーティングパスに影響されなくなる機能です。
引用:https://nextjs.org/docs/app/building-your-application/routing/route-groups
👆この場合、(marketing)
や(shop)
はパスに影響されません。
2. プライベートフォルダ
ディレクトリ名の先頭に_
をつけると、そのディレクトリすべてがルーティングから除外される機能です。
引用:https://nextjs.org/docs/app/building-your-application/routing/colocation
👆この場合、_lib
配下はすべてルーティングから除外されます。
3. Parallel Routes
ディレクトリ名の先頭に@
をつけると、1つのページの中に複数のページを表示できる機能です。
引用:https://nextjs.org/docs/app/building-your-application/routing/parallel-routes
ページが分かれているので、以下のようにerror.tsx
などを別々に指定できたりします👇
引用:https://nextjs.org/docs/app/building-your-application/routing/parallel-routes
4. Intercepting Routes
現在のレイアウトを保ったまま別ページを表示させることができる機能です。
たとえば、以下のように配置すると👇
引用:https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes
以下のような動きになります👇
「/feedにいるときに/photo/123に移動すると、本来ならphoto/[id]/page.js
の内容が表示されるはずだけど、/feedのレイアウトを保ったまま(..)photo/[id]/page
の内容が表示される」
これを利用することで、以下のような動きを実現できます👇
-
/
から/photos/2
にアクセスした場合:
→/
のレイアウトをそのままに写真を表示できる -
/photos/2
に直接アクセスした場合:
→写真だけが表示される
UXにこだわりたい人向けの機能な気がします。
詳しく知りたい場合は、以下の記事が参考になります👇
Next.js の Interception Routes について
公式Exampleはこちら👇
https://nextgram.vercel.app/(GitHub)
getStaticPropsとgetServerSideProps がなくなった
以下のように変わりました👇
-
getServerSideProps
→ 廃止 -
getStaticProps
→ 廃止 -
getStaticPaths
→ generateStaticParams
サーバーコンポーネントという概念を理解できていれば「そりゃそうだよね」という話ですが、サーバーコンポーネント内で直接非同期処理を書けるので1.と2.は「もういらないよね」となりました。
SEOタグを簡単に設定できるようになった
page.tsx
かlayout.tsx
で、metadata
というオブジェクトをexportするだけで、SEOタグができるようになりました👇
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: '...',
description: '...',
}
動的に設定したい場合は、generateMetadata
という関数をexportします。
OGPが簡単に設定できるようになった
以下のファイル名の画像をpage.tsx
やlayout.tsx
と同階層に置くだけで、OGPが設定できるようになりました👇
opengraph-image.(jpg|jpeg|png|gif)
twitter-image.(jpg|jpeg|png|gif)
拡張子を以下のようにすることで、動的にOGPを生成することもできます👇
opengraph-image.(js|ts|tsx)
twitter-image.(js|ts|tsx)
ルーティング系のフックがいろいろ変わった
以下のように変わりました👇
-
next/router
からではなく、next/navigation
からuseRouter
をimportするようになった - 以下のフックが追加された
useSearchParams
usePathname
useParams
- これらすべてのフックはクライアントコンポーネントでしか使えなくなった
- なので
"use client"
なファイルに切り出す必要がある
- なので
SSR / ISR の設定方法が変わった
Page Routerのときは、以下の2つでSSR / ISRの設定ができました👇
getServerSideProps
getStaticProps
ですが先述した通り、これらは廃止されました。
その代わりにfetchのオプションでSSR / ISR の設定ができるようになりました。
どういうことかというと、まずサーバーコンポーネント内でfetch関数を使った場合、基本的に結果がキャッシュされるようになりました。
たとえば、以下のように書くと、永続的にキャッシュされます。
fetch('https://...') //🟢
たとえば、以下のように書くと、1時間だけキャッシュされます。
fetch('https://...', { next: { revalidate: 3600 } }) //🟡
たとえば、以下のように書くと、キャッシュされずに毎回fetchされます。
fetch('https://...', { cache: 'no-store' }) //🔴
この場合、以下のようになります。
- 🟢のようなfetchしか存在しないページ:SSG
- 🟡のようなfetchが存在するページ:ISR
- 🔴のようなfetchが存在するページ:SSR
また、fetch以外の処理(たとえばDBからのデータ取得)もcacheという関数でラップすることで、キャッシュできます👇
import { cache } from 'react'
export const revalidate = 3600 // revalidate the data at most every hour
export const getItem = cache(async (id: string) => {
const item = await db.item.findUnique({ id })
return item
})
具体的に
- どういうときにキャッシュされるのか?
- どういうときにキャッシュが破棄するのか?
については、書き方のパターンがいくつもあります。
たとえば以下のように書くことで、コンポーネント単位でfetchのオプションを設定できたりしますし(これは従来のgetStaticProps
でrevalidate
を設定するのと同等)、
...
export const revalidate = 3600
...
cookieを使うようなコードを書いた場合、「ユーザーによって表示させる内容は違うよね」と自動で判断されるので、キャッシュされなかったりします。
fetchしたデータ → コンポーネント間で受け渡す必要なし
親と子で同じ内容をそれぞれfetchした場合、「1回だけ実行すればいいよね」と自動的に判断されて、1回しかfetchされません。
この仕組みを「Request Memoization」と呼びます。
なのでわざわざ「二重fetch防止のために親でfetchしたデータを子に渡す」みたいなことをする必要はありません。
公式ドキュメントでは「好きな場所で好きなだけfetchしたらいいんだぜ」(意訳)と書かれています👇
deeple翻訳
ツリー内の複数のコンポーネントで同じデータ(現在のユーザーなど)を使用する必要がある場合、グローバルにデータをフェッチしたり、コンポーネント間でpropを転送したりする必要はありません。
代わりに、同じデータに対して複数のリクエストを行うことによるパフォーマンスへの影響を心配することなく、データを必要とするコンポーネントでフェッチまたはReactキャッシュを使用できます。
SSGの設定方法が変わった
以下のように変わりました。
-
🚫Page Router
next build & next export
を実行する。 -
✅App Router
next.config.js
にoutput: "export"
を追加して、next build
を実行する。
(next export
が不要になった)
🤔知っておいた方が良さげなこと
「これ知っておいたほうが良いな」と思ったことをいろいろ書きます。
従来のUIコンポーネントはサーバーコンポーネントに非対応
あくまで「現状は非対応」という話なのですが、たとえば以下などのUIコンポーネントはApp Routerに対応していません。
- Chakra UI
- MUI
対応していない理由は「これらのライブラリはemotionに依存していて、かつemotionはサーバーコンポーネントで動作しない。なのでApp Routerに対応していない。」になります。
なので逆に言えば、"use client"
を付けたクライアントコンポーネントの中であれば、これらのUIコンポーネントは今まで通り使えます。
たとえば、Chakra UIのページでも「"use client"
を付けてChakraProviderをラップしたらいいぜ」と案内されています👇
ただ、このやり方だとサーバーコンポーネントのメリットを殺してしまいます。
なのでサーバーコンポーネントのメリットを活かすなら、現状だと
- 何らかのHeadlessコンポーネント(たとえばRadix UIなど)+何らかのzero-runtime系のCSSフレームワーク(たとえばTailwind CSSなど)
を組み合わせるのがベターなのかなと思います。
サーバーコンポーネントとクライアントコンポーネントの混合
基本的には以下のとおりです。
- ✅クライアントコンポーネントからクライアントコンポーネントを呼び出す
- ✅サーバーコンポーネント からクライアントコンポーネントを呼び出す
- ❌クライアントコンポーネントからサーバーコンポーネント を呼び出す
ただし、最後のケースにおいても、クライアントコンポーネントのchildrenとしてサーバーコンポーネントを渡すことで✅にできます。
Instrumentation
「サーバー起動時に1回だけ実行したい!」な処理を書ける機能です。
以下のような感じでプロジェクトの直下にinstrumentation.ts
というファイルを用意して、register
という関数をexportするだけです。
export function register() {
//サーバー起動時に1回だけ実行したい処理
}
たとえば「サーバーリッスンする前にDBに接続する」な用途に使えそうです。
- 今の時点でまだexperimentalなので、
next.config.js
をゴニョゴニョする必要あり。 - プロジェクトの直下に置くので、Page Routerのプロジェクトでも使えます。
Server Actions
一言でいうと「クライアント側のコードにサーバー側のコードを直接書ける機能」です。
以下、公式のサンプルです。
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>
);
}
//https://nextjs.org/blog/next-13-4#server-actions-alpha
ヤバイですよねこれ。異次元の便利さです。
現時点でまだαなので本番で使うのは厳しいですが、stabledになったら小規模~中規模くらいのプロジェクトは「もう全部Next.jsでいいじゃん」となるかもなので、「こういうのがあるんだな」くらいには知っておいた方が良いと思います。
日本語で解説してくれている方もいます👇
4種類のキャッシュ
4種類のキャッシュの仕組みが存在します👇
引用:https://nextjs.org/docs/app/building-your-application/caching
雑にまとめると、以下のような感じです👇
- 🟥Router Cache
- 「一度表示したページはクライアント側にキャッシュがあるんだから、それ使えばサーバーにリクエスト送らなくてよくなるじゃん」なやつ。
- ただし、永続的にキャッシュが有効なわけではなくて以下のケースで無効化される。
- 任意の時間が経ったとき(たとえばStatic Routeなページだと5分)。
- ページをリロードしたとき。
- 🟦Full Route Cache
- 「一度生成したページはサーバー側にキャッシュがあるんだから、リクエストされたらそれ返せばいいじゃん!」なやつ。
- 基本的には永続化されるけど、以下のケースだと永続化されない。
- fetchでrevalidateなどを指定している場合 → 指定したタイミングでキャッシュが再生成されたり、そもそも生成されなかったりする
- cookies関数のような「ユーザーによって返す値が違ってくるじゃん」な関数(Dynamic Functionsと呼ぶ)を使っている場合 → そもそもキャッシュが生成されない。
- 🟨Request Memoization
- 「複数のコンポーネントで同じ内容を何回もfetchしてるぞ~?よーし、1つにまとめちゃえ!」なやつ。
- Next.jsの機能ではなく、Reactの機能。
- GETメソッドにのみ適用される。
- POSTメソッドやDBへの取得処理などもメモ化したい場合は、Reactのcacheを使う。
- 🟪Data Cache
- 「これさっきfetchしたやつと同じだから結果を使いまわしたろ!」なやつ。
- 基本的には永続化されるけど、以下のケースだと永続化されない。
- fetchでrevalidateなどを指定している場合 → 指定したタイミングでキャッシュが再生成されたり、そもそも生成されなかったりする
- revalidatePathなどを使えば任意のタイミングでキャッシュを削除できる。
🟥と🟨は、すぐに消えるしユーザー単位なので、あまり意識しなくていいのかも。
🟦と🟪は、基本的にユーザー全体&デプロイメントに渡って永続化されるので、意識しないとバグになるかも。
ちなみに🟦は1つのデプロイメントでだけ有効ですが、🟪は複数のデプロイメント間に渡って有効らしいです。なので🟪は再デプロイしても残ります。
server only
server onlyというパッケージをnpm installして、たとえばA.tsx
の先頭にimport 'server-only'
と書きます。
そうすると、A.tsx
をクライアントコンポーネントから呼び出している場合にビルドエラーが出るようにできます。
statically-typed-links
Link
コンポーネントのhref
プロパティの型チェックを有効にできるやつです。
- 今の時点でまだexperimentalなので、
next.config.js
をゴニョゴニョする必要あり。
Page Router → App Routerへの移行
ガイドがあります👇
おわり
\ PR /
株式会社PrAhaでは、エンジニアを募集しています!
- ものづくりが好きな優しいギークが集まってる
- フルリモート・フルフレックス
- 年収618〜1,069万円(正社員の場合)
- 充実した福利厚生
Discussion