メインコンテンツへスキップ
メインコンテンツへスキップ

Article

Next.js App Router の ISR でハマった3つの落とし穴と解決策

Published
Updated

Next.js App Router の ISR(Incremental Static Regeneration)は、ビルド時に静的ページを生成しつつ、バックグラウンドで更新をかけることで高速性とリアルタイム性を両立する強力な機能です。しかし、その導入にはいくつかの注意点があり、私たちも実装で戸惑うことがありました。

  • revalidate オプションはサーバコンポーネントのみ有効。クライアントコンポーネントでは機能しない。
  • 動的ルーティングと ISR の組み合わせには、キャッシュの挙動を深く理解する必要がある。
  • fetch API 以外のデータ取得では、キャッシュ無効化の設定が重要。

revalidate オプションが効かない?サーバコンポーネントの原則

ISR の revalidate オプションは、Next.js App Router では fetch API に紐づいています。クライアントコンポーネントで fetch 以外のデータ取得方法を使っていると、期待通りにページが更新されないことがあります。

// app/page.tsx (Server Component)
async function getData() {
  const res = await fetch('https://api.example.com/data', { next: { revalidate: 60 } });
  if (!res.ok) {
    throw new Error('Failed to fetch data');
  }
  return res.json();
}

export default async function Page() {
  const data = await getData();
  return (
    
      

{data.title}

{data.content}

); }

動的ルーティングにおける ISR キャッシュの複雑性

[slug] のような動的ルーティングと ISR を組み合わせる場合、各ページのキャッシュ生成と更新のタイミングを正確に把握することが重要です。特に generateStaticParams を使用しない場合、リクエスト時に初めてページが生成され、その後更新される挙動になります。

  • generateStaticParams で事前にパスを生成すると、初回アクセス時にキャッシュが作られる。
  • generateStaticParams を使わない場合、最初のアクセスでページが生成され、その後のリクエストで ISR が機能する。
  • revalidate はパスごとに独立して適用される。
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';

export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts').then((res) => res.json());
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

async function getPost(slug: string) {
  const res = await fetch(`https://api.example.com/posts/${slug}`, { next: { revalidate: 3600 } });
  if (!res.ok) {
    notFound();
  }
  return res.json();
}

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);
  return (
    
      

{post.title}

{post.body}

); }

fetch 以外のデータ取得とキャッシュ無効化

Next.js App Router の ISR は fetch API と密接に連携していますが、Axios や ORM など fetch 以外のライブラリでデータを取得する場合、明示的なキャッシュ無効化が必要です。これを怠ると、期待するタイミングでコンテンツが更新されない問題が発生します。

  • cache: 'no-store' を指定し、常に最新のデータを取得する。
  • revalidatePathrevalidateTag を使用し、必要に応じてキャッシュを再検証する。
// lib/data.ts
import { unstable_noStore as noStore } from 'next/cache';
import axios from 'axios';

export async function getLatestData() {
  noStore(); // この関数が常に最新データを取得するように設定
  const response = await axios.get('https://api.example.com/latest');
  return response.data;
}

// app/dashboard/page.tsx
import { getLatestData } from '../../lib/data';

export default async function DashboardPage() {
  const data = await getLatestData();
  return (
    
      

最新データ: {data.value}

最終更新: {new Date(data.timestamp).toLocaleString()}

); }

Next.js App Routerでの開発や ISR の導入について、さらに深い知見や具体的な課題があれば、ぜひ私たちと一緒にカジュアル面談で意見交換しませんか?

よくある質問

Next.js App Router で ISR を使うメリットは何ですか?

ビルド時に静的ページを生成するため初回表示が高速で、かつバックグラウンドでコンテンツを更新できるため、常に新鮮な情報をユーザーに届けられます。

ISR がうまく機能しているか確認する方法はありますか?

ページの初回アクセス後に更新を待ち、再度アクセスしてコンテンツが更新されているかを確認します。Vercel のデプロイログや Next.js の開発サーバーのコンソール出力も参考になります。

Join the team

トレックスの技術組織で働く

記事の内容に共感したら、ぜひ現場のエンジニアと話してみてください。