Next.js v15 App Router のfetch戦略について
Next.jsのfetchについてはいくつかパターンがあるので頭の中を整理するためにも記載します。 まず、データフェッチのパターンについての洗い出しから
- ServerComponentsを利用してデータ取得
- ServerActionsを利用
- ClientComponentsを利用したfetch
従来のクライアントコンポーネントを利用したfetchについては SWR等を利用してデータ取得を行うものこのあたりについてはあまり深く追求しない。 そのため本記事で取り上げるのはServerComponentおよびServerActionsについて
ServerComponentsを利用してデータ取得
基本的にデータを表示させたいコンポーネントから近い場所でデータを取得する Next.jsの現在推奨とされているデータ取得パターンになる。
サーバーコンポーネントからfetch処理をさせてあげる必要がある。
export default async function Page() {
const data = await fetch('https://api.example.com/posts').then(res => res.json());
return <ClinetComponent initialData={data} />
}
※ サーバーコンポーネントのためHooksが利用できない
Next.js15よりキャッシング動作に変更が入っている。 デフォルトでキャッシュされなくなっています。
そのためFetchOptionに明示的に値を渡す必要があります。
// キャッシュされないため毎回データを取得する
const data = await fetch('https://api.example.com/posts').then(res => res.json());
// リアルタイムデータ or 完全静的
// 明示的にキャッシュする
const data = await fetch('https://api.example.com/posts', { cache: 'force-cache' }).then(res => res.json());
// 明示的にキャッシュを無効
const data = await fetch('https://api.example.com/posts', { cache: 'no-store' }).then(res => res.json());
// 時間ベースの再検証
// 再検証付きキャッシュ(指定した時間ごとに更新 例:60秒ごと)
const data = await fetch('https://api.example.com/posts', { next: { revalidate: 60 } }).then(res => res.json());
// オンデマンド再検証
// 特定のキャッシュをタグ付けし、呼び出し時に使えるようにする
const data = await fetch('https://api.example.com/posts', { next: { revalidate: tags: ['products'] } }).then(res => res.json());
どのようなタイミングで利用するか?
リアルタイムデータ
force-cache
株価や為替レート、ライブスコアなどリアルタイムに表示させたいコンテンツ向き
完全静的
force-cache
もしくは一度取得してしまった後更新がないもの。
静的コンテンツでビルド時のみにデータを取得し永続的にキャッシュさせたいもの。
時間ベースの再検証
revalidate
ISRと呼ばれる手法。
時間指定ごとに再検証するもの。リアルタイムに更新しなくてもよいが、もう少し緩めにリアルタイム性を求めたい場合などに利用する。
ニュース記事やブログ投稿といったコンテンツ向き
オンデマンド再検証
revalidate
手動で再検証などをするときもこちらのオプションを活用します
ISRとは概念が異なります。
特定のキャッシュを瞬時に更新する仕組みのことを指します。
API等をトリガーにしてキャッシュされた対象を即座に更新。
ServerActionsを利用
ServerActionsはサーバーサイドの関数をクライアントから直接呼び出せる機能。 そのためRouteHandlerでAPIを立てなくても実行が可能になる。
アクション
'use server';
export async function createPost(formData) {
const title = formData.get('title'); const content = formData.get('content');
const post = await db.posts.create({ data: { title, content } });
revalidatePath('/posts');
}
呼び出し側
import { createPost } from '@/app/actions/post.ts';
export default function ClientComponent() {
return (
<form action={createPost}>
<input name="title" placeholder="タイトル" required />
<textarea name="content" placeholder="本文" required />
<button type="submit">送信</button>
</form>
);
}
まとめ
Next.jsはフルスタック故に設計が複雑化してきています。 特にRSCが出てきてからはできることが増えすぎたことで設計が難しくなった印象です。 個人的な主観としては、何をしないかを明確にしておいたり、方向性をある程度決めた上で取り組んだほうがよい気がします。 Next.jsはできることが多いのでエンジニアの設計力が試されるますね。