【Next.js】Next.js 15で変更された機能と今後のキャッシュ
IT技術
はじめに
2024/10/21にNext.js 15がリリースされました!
今回のリリースでは、安定版(stable)から実験的機能(experimental)まで、様々な新しい機能が追加されています。
全部を紹介したいところですが、今回は特に知っておきたい変更とおまけとして新しいキャッシュのモード(experimental)について見ていこうと思います。
キャッシュの変更
今までのCacheと一番変わった点は、Get リクエストのRoute Handler と Client Router Cacheのキャッシュがデフォルトでは無効になった点でしょう。
特に考えなくても(勝手に?)パフォーマンスの良いアプリケーションを作成できると言った点でキャッシュがデフォルトでされていたようですが、ユーザーからのフィードバックや今後のPartial Prerenderingとの相互作用などを考えられ無効になりました。もちろん明示的に指定することで以前のようにキャッシュすることができます。
Router Handlerのキャッシュの例:
1export const dynamic = 'force-static’
ただ、sitemap.ts
, opengraph-image.tsx
、や icon.tsx
などの metadata files以前通りデフォルトでキャッシュされるようなので気をつけなければいけない点です。
また、Route HandlerだけでなくClient Router Cacheもデフォルトで無効となりClientのページ遷移時にはデフォルトで常に最新のページが表示されるようになりました。Client Router Cacheは以前の私の記事でも書かせていただいたとおり、動的なページであろうと最低でも30秒間キャッシュされ、その間にそのページに遷移した場合はキャッシュされたページ情報が表示されていました。
Next.js 15ではデフォルトでこのキャッシュが無効になりましたが、next.config.js
に設定を追記することで以前のようにキャッシュを有効化できるようです。(まだexperimentalのようですが)
1const nextConfig = {
2 experimental: {
3 staleTimes: {
4 dynamic: 30,
5 },
6 },
7};
8
9export default nextConfig;
ただ、Client Routerのデフォルトでのキャッシュ無効化になりましたが、
layout.tsx
ファイルをページ遷移間で共有している場合、そのレイアウトはリレンダリングされないのでその中でデータ取得している場合は再取得されません(これは来るPPRに備えてのことのようです)- ブラウザバック/フォワードでのページ遷移では以前同様にキャッシュが使用されます
loading.js
でのキャッシュは5分間行われます。
非同期リクエストAPI
リクエストで送られてくるデータを取得するAPIが同期的から非同期的に変更になりました。
変更になったAPIは下記になります。
cookies
headers
draftMode
params
(layout.js
,page.js
,route.js
,default.js
,generateMetadata
,generateViewport
の中で使用されている場合)page.js
で使用されているsearchParams
従来の同期的方法だと、レンダリングが開始される前にリクエストで送られてきたデータを上記APIのために準備(データの整形などの処理)する必要があり、上記APIを使用していない場合でも一律にその間待機しないといけなかったため、非同期化すると言った対応が入ったものと思われます。(また、将来的な最適化のためでもあるそうです)
非同期化することで各APIの準備が整っていない場合でも、各APIを使用しない箇所を先んじてレンダリングすることができるようになりページの表示速度が上がります。
この変更は破壊的変更となるため、CLIツールを使った自動変更が用意されています。
1$ npx @next/codemod@canary next-async-request-api .
今後のNext.jsのキャッシュモード(おまけ)
Next.js 15にはまだ組み込まれていなくまだ実験段階のものですが、新しいキャッシュモードとして 「dynamic IO」というコンセプトがあるようなのでおまけとして載せておきます。(おまけと言いつつ結構量あります笑)
というのも、現在のNext.jsのキャッシュはdyanmic functionやfetch cacheの設定などでキャッシュを取り巻く環境が複雑になりすぎていることが懸念されています。そのためもっとシンプルにキャッシュを使えるようにしようというのがこれから紹介する <Suspense>
と use cache
を使用した新しいコンセプトのようです。
このコンセプトの面白い点はページの実装がNext.jsの想定とそぐわない場合はエラーになり、強制的にユーザーにキャッシュをするのかしないかを選ばせる点です。
以下はエラーになるコードです。
1async function Component() {
2 return fetch(...) // error
3}
4
5export default async function Page() {
6 return <Component />
7}
今までのNext.jsであれば全く問題のないコードですが、新しいコンセプトではこのページは静的にするのか動的にするのかを明示的に示されていないためエラーになります。
上記の例ではFetchの使用によりエラーが出ていますが、クッキーの取得でもrequest headerの取得でもasync な Node APIの使用であれば全てエラーになるようです。
動的なページ
ではどのように、動的なページ(リクエスト時に毎回データ取得してレンダリングされるページ)を作成できるのでしょうか。
ここで出てくるのが <Suspense>
です。
1async function Component() {
2 return fetch(...) // no error
3}
4
5export default async function Page() {
6 return <Suspense fallback="..."><Component /></Suspense>
7}
非同期処理を使用する場合は <Suspense>
の中に入れることで、このページは動的であると示していることになるようです。
静的なページ
では、逆に静的なページはどうするのでしょうか。
静的なページを作成するには、このコンセプトのもう一つの登場人物である use cache
の使用によりできます。
1"use cache"
2
3export default async function Page() {
4 return fetch(...) // no error
5}
use cache
を使うことでこのページは静的なページとなり、内部のデータフェッチなどは全てキャッシュされるようです。
動的 x 静的なページ
さらに動的と静的を併せ持つページも作成できます。
1// app/layout.tsx ------------------
2"use cache"
3
4export default async function Layout({ children }) {
5 const response = await fetch(...)
6 const data = await response.json()
7 return <html>
8 <body>
9 <div>{data.notice}</div>
10 {children}
11 </body>
12 </html>
13}
14
15// app/page.tsx -------------------
16import { Suspense } from 'react'
17async function Component() {
18 return fetch(...) // no error
19}
20
21export default async function Page() {
22 return <Suspense fallback="..."><Component /></Suspense>
23}
<Suspense>
と use cache
の合わせ技で、上記の例だとlayoutファイルはキャッシュされ、ページ内のコンポーネントはリクエスト時に毎回データが取得される動的な部分となります。
Cache Functions
上記で紹介した use cache
はコンポーネント全体に適用させる他に、Server Actionsのようにfunctionとして設定させることもできます。
1async function getNotice() {
2 "use cache"
3 const response = await fetch(...)
4 const data = await response.json()
5 return data.notice;
6}
7
8export default async function Layout({ children }) {
9 return <html>
10 <body>
11 <h1>{await getNotice()}</h1>
12 {children}
13 </body>
14 </html>
15}
このように設定しても、静的なページで示したのと同じ結果になります。
ではこのようにすることで何がいいのかというと、後続のデータ取得の処理の追加などにより想定しない挙動を抑えることができる点と書かれています。
コンポーネント全体に use cache
適用させる場合、そのコンポーネントでデータ取得処理を書くと全てキャッシュされます。その場合、誤って動的で欲しいデータの処理を追加すると予期せぬ挙動になります。その点、Cache Functionsによりキャッシュの定義を書くと、コンポーネントで追加されたデータの取得処理に <Suspense>
や use cache
が使用されていない場合はエラーになり、想定しない挙動を抑えることができるようです。また cache functionsを使用することで、静的な部分と動的な部分を併せ持つコンポーネントなどに対しもて細かくキャッシュを設定することができます
キャッシュの設定
現状のNext.jsのキャッシュのように、キャッシュをクリアできる機能もあります。
キャッシュのクリア方法は現状と同じ、オンデマンド方式と時間方式になります。
オンデマンド方式
1import { cacheTag } from 'next/cache';
2
3async function getNotice() {
4 'use cache';
5 cacheTag('my-tag');
6}
オンデマンド方式は、キャッシュにタグを設定しServer ActionsからrevalidateTag('my-tag')
を呼ぶことでできるようです。今までと同じですね。
時間方式
1"use cache"
2import { unstable_cacheLife as cacheLife } from 'next/cache'
3
4export default async function Page() {
5 cacheLife("minutes")
6 return ...
7}
時間方式では CacheLife
functionを使用してキャッシュがクリアされる時間を設定するようです。
設定できる引数はデフォルトで
"seconds"
"minutes"
"hours"
"days"
"weeks"
"max"
が用意されているようですが独自の値を設定することもできるみたいです。
まとめ
上記で紹介した以外にもNext.js 15では色々な機能の追加などがありますので気になる方は公式サイトを見に行ってみてください。
Partial PreRenderなど今後安定化になる大きな機能なども色々とあるようなので今後のNext.jsのリリースにも期待が高まりますね。
また、おまけで書いたNext.jsの新しいキャッシュモード(dynamic IO)の登場によりキャッシュの利用がよりシンプルに、厳格になっていきそうです。前に記事でApp Routerのキャッシュについてまとめた時も書きましたが、現状のキャッシュは色々と認識しないといけない部分が多く想定しない挙動が生まれやすいと感じていたので、今後はよりシンプルかつエラーで気づかせてくれるようになるのはありがたい点だと感じました。
dynamic IOはまだまだ実験段階のようですので、上記で説明した以外の機能ができたり、上記とは違う方法になったりもすることがあると思うので今後に期待したいところです。
ではではまた次の記事でお会いしましょう!
参考記事
https://nextjs.org/blog/next-15
https://nextjs.org/blog/our-journey-with-caching
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
Udemy信奉者系フロントエンジニア(バックエンドもちょっと)。 現在はNextjsを用いた不動産情報サイトのフロントエンド開発担当中。 映画好きで基本毎日Netflixしてます。