404 Not Found

404 Not Found


nginx

Next.js でのデータ取得と API

ブログをApp Routerに移行した後、トムは新たな問題に直面しました。ページによってデータの更新頻度が大きく異なっていたのです。記事は月に1回、商品の価格は1日に数回、ユーザーのアバターはログインするたびに変わる可能性がありました。彼は、各データタイプの特性に応じて異なるデータ取得戦略を選択しつつ、フロントエンド向けに統一されたAPIインターフェースを提供する必要がありました。データ取得のためのNext.jsのサーバーコンポーネントとルートハンドラーは、まさにこれらの課題を解決するために彼が必要としていたものでした。


1. 学習内容


2. 概念図

トムは、Next.jsにおけるデータ取得の全プロセスを概説しました。ページリクエストが届くと、Next.jsはデータ取得戦略に基づいて、データをキャッシュするか、再検証するか、あるいはAPIにルーティングするかを判断します。このプロセスを理解することで、彼はデータの種類に応じて適切なアプローチを選択できるようになります。

フローチャート内の決定ノード「{Caching Strategy}」は、このセクションの中核をなすものであり、データ取得のパフォーマンスとリアルタイム性能を決定づける。force-cacheは最速ですが、リアルタイムではありません。no-storeは最もリアルタイム性が高いですが、パフォーマンスは最も低くなります。revalidatetagsは、この2つの要素の間で柔軟なトレードオフを実現します。ルートハンドラとミドルウェアは、それぞれAPIへのアクセス処理とリクエストのインターセプトを行う補助的なメカニズムとして機能します。

100%
flowchart TD
    A[Page Request] --> B{Server Component}
    B --> C[fetch Data]
    C --> D{Caching Policy}
    D -->|force-cache| E[Read Cache<br/>Default SSG]
    D -->|no-store| F[Request every time<br/>Real-time SSR]
    D -->|revalidate: N| G[Cache N seconds<br/>ISR Pattern]
    D -->|{next: {tags: [...]}}| H[Cache by Tag<br/>On-Demand ISR]
    E --> I[Back HTML]
    F --> I
    G --> I
    H --> I
    I --> J[Route Handler<br/>/api/*]
    J --> K[Database/External API]
    I --> L{To be verified?}
    L -->|is | M[Middleware<br/>Auth/Redirect]
    L -->|No| N[Direct Rendering]

3. 実際の事例

ブログをApp Routerに移行した後、トムはデータ取得に関して新たな課題に直面しました。以前はgetStaticPropsgetServerSidePropsを使用していましたが、これらは使い慣れていたものの、柔軟性に欠けていました。例えば、記事一覧は1時間に1回更新する必要がありましたが、人気記事が編集された場合は即座に更新する必要がありました。しかし、getStaticPropsではページごとにこの処理を行うことができませんでした。

App Router は、まったく新しいデータ取得のパラダイムを導入しています。fetch はサーバーコンポーネント内で直接使用され、next.revalidate および next.tags はキャッシュ戦略を精密に制御するために使用されます。また、Tom はルートハンドラーを使用して、コメントセクションのフロントエンドコンポーネントが呼び出せる API エンドポイントを公開し、ミドルウェアを用いてダッシュボードのルートを保護しています。以下は、彼がブログ用に設計したデータ取得アーキテクチャです。

彼はまず、ブログ上のすべてのデータソースの特徴を分析しました。具体的には、記事コンテンツ(1時間ごとに更新され、キャッシュ可能)、コメント(リアルタイムで更新され、即座に表示する必要がある)、統計情報(アクセスごとに変動する)、製品情報(1日に数回更新され、更新後できるだけ早く表示する必要がある)です。その後、データタイプごとに異なるフェッチ戦略を選択しました。この分析を通じて、彼はNext.jsのデータフェッチ設計の素晴らしさを実感しました。それは、画一的な「SSGかSSRか」というアプローチではなく、ページ単位、リクエスト単位、さらにはデータタグ単位で精密な制御を可能にするものだったのです。

(1) サーバーコンポーネント:データ取得

await fetchはサーバーコンポーネント内で直接使用できます。これはApp Routerの最も強力な機能の一つです。useEffectも、SWRやReact Queryも、追加のクライアントサイド状態管理ライブラリも必要ありません。コンポーネント関数内で直接データをリクエストするだけで、サーバーサイドレンダリングが完了すると、そのデータはHTMLとともにブラウザに送信されます。

fetch の 2 番目のパラメータには、next の設定を含むオブジェクトを指定します。これはキャッシュの動作を制御するために使用されます。{ cache: 'force-cache' } は SSG と同等であり、ビルド時にデータが 1 回だけ取得されます。{ cache: 'no-store' }はSSRに相当し、リクエストごとにデータが新たに取得されます;{ next: { revalidate: 60 } }はISRに相当し、60秒間のキャッシュ後にデータが再検証されます。

さらに高度な活用例として、「オンデマンド ISR」があります。next: { tags: ['posts'] } を使用してデータにタグを付け、その後、ルートハンドラーまたは管理ページで revalidateTag('posts') を呼び出すことで、手動で更新をトリガーします。これにより、再検証期間が終了するのを待つことなく、編集直後に記事を再生成することができます。

3つのキャッシュ戦略に関する決定表

戦略 設定の取得 動作 ユースケース
静的 SSG cache: 'force-cache' ビルド時に一度取得され、その後はすべて CDN 経由で配信 記事コンテンツ、About ページ
リアルタイムSSR cache: 'no-store' リクエストごとに更新 ユーザーデータ、リアルタイムダッシュボード
増分ISR next: { revalidate: 60 } キャッシュに保存後60秒後に再検証 製品一覧、価格ページ
オンデマンド更新 next: { tags: ['x'] } キャッシュ + revalidateTag() を指定して更新を実行 CMSコンテンツの編集後、即座に更新

トムが戦略を選択する際の原則:決定は、データ変更の頻度とリアルタイム性の要件に基づいて行われる。記事の内容(変更頻度が低い)にはSSGを、コメント数(変更頻度は高いが多少の遅延は許容できる)には30秒ごとに更新されるISRを、ユーザーのアバター(即時更新が必要)にはSSRを採用している。これらの戦略を適切に組み合わせることで、パフォーマンスとリアルタイム応答性の最適なバランスを見出すことができる。

(1) ▶ サンプル:サーバーコンポーネントの 3 つのキャッシュ戦略

TSX
// app/posts/page.tsx - Strategy 1:force-cache(Default SSG)
// Data is retrieved only once during build time.,All subsequent requests go through CDN cache
async function PostsPage() {
  const posts = await fetch('https://api.example.com/posts', {
    cache: 'force-cache'  // equivalent to SSG,Default behavior
  }).then(r => r.json())

  return (
    <div>
      <h1>List of Articles</h1>
      {posts.map((post: any) => (
        <article key={post.id} style={{ marginBottom: 16 }}>
          <h2>{post.title}</h2>
          <p>{post.body.slice(0, 100)}...</p>
        </article>
      ))}
    </div>
  )
}
export default PostsPage

// app/dashboard/stats/page.tsx - Strategy 2:no-store(Real-time SSR)
// Every request starts from API Get the latest data
export const dynamic = 'force-dynamic'

async function StatsPage() {
  const stats = await fetch('https://api.example.com/dashboard/stats', {
    cache: 'no-store'  // Request the latest data every time
  }).then(r => r.json())

  return (
    <div>
      <h1>Real-Time Statistics</h1>
      <p>Users Online:{stats.onlineUsers}</p>
      <p>Today PV:{stats.pageViews}</p>
      <p>API Number of calls:{stats.apiCalls}</p>
    </div>
  )
}
export default StatsPage

// app/products/page.tsx - Strategy 3:revalidate(ISR Incremental Update)
// Cache 60s, first visit after 60s triggers a regenerate
async function ProductsPage() {
  const products = await fetch('https://api.example.com/products', {
    next: { revalidate: 60 }  // ISR:60 Please try again in a few seconds.
  }).then(r => r.json())

  return (
    <div>
      <h1>Product List</h1>
      {products.map((p: any) => (
        <div key={p.id} style={{ border: '1px solid #ddd', padding: 12, marginBottom: 8, borderRadius: 8 }}>
          <h3>{p.name}</h3>
          <p>Price:${p.price}</p>
          <p>Inventory:{p.stock > 0 ? 'In stock' : 'Out of stock'}</p>
        </div>
      ))}
    </div>
  )
}
export default ProductsPage

// app/api/revalidate/route.ts - On-Demand ISR Refresh on Demand
// Call this after the administrator edits the article API Refresh the cache now
import { revalidateTag } from 'next/cache'

export async function POST(request: Request) {
  const body = await request.json()
  const { tag } = body

  // Verification secret Preventing Abuse
  const secret = request.headers.get('x-revalidate-secret')
  if (secret !== process.env.REVALIDATION_SECRET) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  revalidateTag(tag) // Refresh the cache by tag
  return Response.json({ revalidated: true, tag })
}
▶ 試してみよう

(2) ルートハンドラ API によるルーティング

ルートハンドラーとは、App Router で API エンドポイントを作成するために使用されるメソッドのことです。app/api/ ディレクトリに route.ts という名前のファイルを作成し、HTTP メソッドに対応する名前(GETPOSTPUTDELETEPATCH)の関数をエクスポートします。ルートハンドラーは、サーバーコンポーネントのすべての機能をサポートしています。データベースへのアクセス、環境変数の使用、キャッシュポリシーの制御が可能です。

トムは、前回のブログ記事で紹介したExpressのバックエンドをRoute Handlerに置き換えました。記事のCRUD操作、ユーザー認証、コメントシステムはすべて、Route Handlerを通じてAPIとして公開されています。On-Demand ISRと組み合わせることで、記事の編集直後にキャッシュが更新されるため、ユーザーは再検証期間が終了するのを待つ必要がありません。

ルートハンドラは動的ルーティングもサポートしています。app/api/products/[id]/route.ts では、パスパラメータは params パラメータを介して取得され、これは RESTful API の設計に適しています。また、各ルートハンドラは独自のキャッシュ戦略を設定することも可能です。例えば、GET リクエストに対して { next: { revalidate: 60 } } を設定することで、API レベルのキャッシュを実現できます。

コアルートハンドラの使用に関するクイックリファレンス

ファイルパス HTTPメソッド URLエンドポイント 目的
app/api/posts/route.ts GET /api/posts 記事一覧を取得
app/api/posts/route.ts 投稿 /api/posts 新規投稿
app/api/posts/[id]/route.ts GET /api/posts/1 1つの記事を取得
app/api/posts/[id]/route.ts PUT /api/posts/1 記事を更新
app/api/posts/[id]/route.ts 削除 /api/posts/1 投稿を削除
app/api/auth/login/route.ts 投稿 /api/auth/login ユーザーログイン
app/api/revalidate/route.ts 投稿 /api/revalidate ISRキャッシュの更新

(2) ▶ サンプル:ブログ用 CRUD API の実装

TSX
// app/api/posts/route.ts - List of Articles API(GET)and creating articles API(POST)
import { revalidateTag } from 'next/cache'

// Simulated Database
const posts = [
  { id: 1, title: 'Next.js Getting Started', body: 'This article introduces Next.js Basic Usage...', published: true },
  { id: 2, title: 'React 19 New Features', body: 'React 19 What changes has this brought about?...', published: true },
]

export async function GET() {
  // Return only published posts
  const published = posts.filter(p => p.published)
  return Response.json(published)
}

export async function POST(request: Request) {
  try {
    const body = await request.json()
    const newPost = {
      id: posts.length + 1,
      title: body.title,
      body: body.body,
      published: body.published ?? false,
      createdAt: new Date().toISOString()
    }
    posts.push(newPost)

    // Refresh the article list ISR cache
    revalidateTag('posts')

    return Response.json(newPost, { status: 201 })
  } catch (error) {
    return Response.json({ error: 'Invalid request body' }, { status: 400 })
  }
}

// app/api/posts/[id]/route.ts - Operations on a Single Article(GET / PUT / DELETE)
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const post = posts.find(p => p.id === Number(params.id))
  if (!post) {
    return Response.json({ error: 'Post not found' }, { status: 404 })
  }
  return Response.json(post)
}

export async function PUT(
  request: Request,
  { params }: { params: { id: string } }
) {
  const body = await request.json()
  const index = posts.findIndex(p => p.id === Number(params.id))
  if (index === -1) {
    return Response.json({ error: 'Post not found' }, { status: 404 })
  }

  posts[index] = { ...posts[index], ...body, id: Number(params.id) }
  revalidateTag('posts')
  return Response.json(posts[index])
}

export async function DELETE(
  request: Request,
  { params }: { params: { id: string } }
) {
  const index = posts.findIndex(p => p.id === Number(params.id))
  if (index === -1) {
    return Response.json({ error: 'Post not found' }, { status: 404 })
  }

  posts.splice(index, 1)
  revalidateTag('posts')
  return Response.json({ message: 'Deleted' })
}
▶ 試してみよう

(3) ミドルウェアとリクエストのインターセプト

ミドルウェアは、Next.js の強力なリクエストインターセプト機能です。各リクエストがページに到達する前に実行され、リダイレクト、認証、国際化ルーティング、A/Bテストなどの処理に利用できます。ミドルウェアは Edge Runtime 上で実行され、レイテンシが極めて低い(ミリ秒オーダー)のが特徴です。

トムはミドルウェアを使用して、3つの機能を実装しました。1つ目は、ログインしていないユーザーがダッシュボードにアクセスした際に、ログインページへリダイレクトすること。2つ目は、ユーザーのブラウザの言語設定に基づいて、適切な言語版のページへ自動的にリダイレクトすること。3つ目は、クローラーがAPIルートにアクセスできないようにブロックすることです。

ミドルウェアは、matcherの設定を使用して、インターセプトが必要なルーティングを照合し、それによって不要な実行オーバーヘッドを回避します。なお、ミドルウェアはreq.bodyを読み取ったり、Node.jsのネイティブAPIを使用したりすることはできません。これは、エッジ環境で実行されるためです。

(3) ▶ サンプル:完全なミドルウェア認証システム

TSX
// middleware.ts - Project Root Directory,and  app/ Peer
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  // === Features1:Route Guard ===
  // Routes Protected from Unauthenticated Users → Redirect to the login page
  const token = request.cookies.get('session_token')?.value
  const protectedPaths = ['/dashboard', '/admin', '/profile']

  if (!token && protectedPaths.some(path => pathname.startsWith(path))) {
    const loginUrl = new URL('/login', request.url)
    loginUrl.searchParams.set('redirect', pathname)
    return NextResponse.redirect(loginUrl)
  }

  // === Features2:Logged-in users are redirected to the login page → Redirect to the Dashboard ===
  if (token && pathname.startsWith('/login')) {
    return NextResponse.redirect(new URL('/dashboard', request.url))
  }

  // === Features3:International Routing ===
  // According to Accept-Language Automatically Redirect to Language Version
  const supportedLocales = ['zh', 'en', 'ja', 'pt']
  const defaultLocale = 'zh'

  // Inspection URL Is the language prefix included?
  const pathnameHasLocale = supportedLocales.some(
    locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  )

  if (!pathnameHasLocale) {
    const acceptLanguage = request.headers.get('accept-language') || ''
    const preferredLocale = supportedLocales.find(locale =>
      acceptLanguage.startsWith(locale)
    ) || defaultLocale
    return NextResponse.redirect(new URL(`/${preferredLocale}${pathname}`, request.url))
  }

  // === Features4:Set Response Headers ===
  const response = NextResponse.next()
  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')
  return response
}

// Layout middleware Match Path(Required,Otherwise, all routes will be triggered.)
export const config = {
  matcher: [
    // Match all routes that need to be protected
    '/dashboard/:path*',
    '/admin/:path*',
    '/profile/:path*',
    '/login',
    // Matching Internationalized Routes
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ]
}
▶ 試してみよう

ミドルウェア設定ガイド

設定オプション 説明
matcher ミドルウェアの実行が必要なルート ['/dashboard/:path*', '/login']
request.nextUrl 現在のリクエストの URL オブジェクト パス名や検索パラメータを取得するために使用されます
request.cookies クッキーの閲覧・操作 request.cookies.get('token')
NextResponse.redirect() 指定されたURLへリダイレクト ログイン認証に使用
NextResponse.next() 通常通りリクエストの処理を続行 有効なリクエストを許可
response.headers.set() レスポンスヘッダーの設定 セキュリティ関連のレスポンスヘッダー

ミドルウェアの実行順序と留意点

ミドルウェアは一致したリクエストごとに実行されるため、パフォーマンスが極めて重要です。Edge Runtime はマイクロ秒単位で実行されるように設計されているため、その内部でデータベースクエリや複雑な計算を実行すべきではありません。複数のミドルウェアコンポーネントの実行順序は、matcher で設定された順序によって決定されます。あるミドルウェアコンポーネントが redirect() または rewrite() を返した場合、それ以降のミドルウェアコンポーネントは実行されません。

重要な注意点:ミドルウェア内の環境変数は、NEXT_PUBLIC_ というプレフィックスを付けて公開する必要があるのでしょうか?いいえ、ミドルウェアはサーバー環境で実行されるため、すべての環境変数に直接アクセスできます。ただし、ミドルウェアのコンパイル時に process.env は実際の値に置き換えられるため、実行時に環境変数を動的に読み取ることはできない点に留意してください。


4. 環境変数のセキュリティポリシー

また、トムはルートハンドラーやミドルウェアを使用する際に、環境変数への安全なアクセスという重大な問題に直面しました。Next.js では、環境変数へのアクセスルールはプレフィックスによって決まりますが、これはデータの取得や API 開発において非常に重要です。

環境変数へのアクセスに関するルール

接頭辞 アクセス可能な場所 説明
プレフィックスなし サーバーコンポーネント、ルートハンドラー、ミドルウェア サーバー側でのみ利用可能。ブラウザには公開されない
NEXT_PUBLIC_ すべての場所(ブラウザを含む) はコンパイルされてJSにバンドルされるため、機密情報を含めないでください
NEXT_PRIVATE_ サーバー側でのみ利用可能 プレフィックスのないバージョンと同じ動作をする新しい明示的なマーカー

トムの経験則:データベースの接続文字列、APIのシークレットキー、JWTキーなどの機密情報は、プレフィックスを付けずに、サーバーコンポーネントやルートハンドラー内でのみ使用してください。Google AnalyticsのIDや公開APIのURLなど、フロントエンドで使用する必要がある変数には、NEXT_PUBLIC_というプレフィックスを付けてください。


(1) ▶ サンプル:ISR 増分静的再生成 — ブログ記事ページ

TSX
// app/blog/[slug]/page.tsx - ISR: Regenerate every 60s
interface BlogPost {
  title: string
  content: string
  author: string
  publishedAt: string
}

async function getPost(slug: string): Promise<BlogPost> {
  const res = await fetch(`https://api.example.com/posts/${slug}`, {
    next: { revalidate: 60 },
  })
  if (!res.ok) throw new Error('Post not found')
  return res.json()
}

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

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

  return (
    <article style={{ maxWidth: 700, margin: '0 auto', padding: 24 }}>
      <h1>{post.title}</h1>
      <p style={{ color: '#999', fontSize: 14 }}>
        By {post.author} · {new Date(post.publishedAt).toLocaleDateString()}
      </p>
      <div style={{ lineHeight: 1.8, marginTop: 16 }}>{post.content}</div>
    </article>
  )
}
▶ 試してみよう

(2) ▶ サンプル:サーバー側の処理—フォームの送信とデータの変更

TSX
// app/actions.ts - Server Actions
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string

  if (!title || !content) return

  await fetch('https://api.example.com/posts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title, content, author: 'Alice' }),
  })

  revalidatePath('/blog')
  redirect('/blog')
}

export async function deletePost(slug: string) {
  await fetch(`https://api.example.com/posts/${slug}`, { method: 'DELETE' })
  revalidatePath('/blog')
}

// app/blog/new/page.tsx - New Article Form
function NewPostPage() {
  return (
    <div style={{ maxWidth: 600, margin: '0 auto', padding: 24 }}>
      <h1>New Post</h1>
      <form action={createPost} style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        <input name="title" placeholder="Title" required style={{ padding: 8, borderRadius: 4 }} />
        <textarea name="content" placeholder="Content..." rows={8} required style={{ padding: 8, borderRadius: 4 }} />
        <button type="submit" style={{ padding: 10, background: '#1890ff', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}>
          Publish
        </button>
      </form>
    </div>
  )
}
export default NewPostPage
▶ 試してみよう

❓ よくある質問

Q サーバーコンポーネント内の fetch と、クライアント側の useEffectfetch との違いは何ですか?
A サーバーコンポーネント内の fetch はサーバー上で実行されます。データは HTML としてレンダリングされ、ブラウザに直接送信されるため、ユーザーには読み込み画面が表示されることなく、完成したページが表示されます。一方、useEffectfetchはクライアント側で実行されます。ユーザーにはまず空白のページまたは読み込み画面が表示され、JavaScriptのダウンロードと実行が完了してから初めてコンテンツがレンダリングされます。サーバーコンポーネントのfetch方式は、ファーストスクリーンの読み込みが速く、SEOにもより適しています。
Q ルートハンドラーと従来の Express/Koa バックエンドの違いは何ですか?
A ルートハンドラーは Next.js サーバー上で実行され、別途 Node.js サーバープロセスを必要としないため、デプロイが簡単になります。ただし、ルートハンドラーは軽量なAPI(BFF—Backend For Frontend)に適しています。複雑なバックエンドロジックの場合は、依然として独立したバックエンドサービスの使用が推奨されます。ルートハンドラーの利点は、同じプロジェクト内のフロントエンドコードと型定義を共有でき、単一のユニットとしてデプロイできることです。
Q ミドルウェアでは何ができるのか、何ができないのか?
A ミドルウェアは Edge Runtime(軽量な JavaScript 環境)で実行されます。可能な操作には、Cookie の読み取り・設定、URL のリダイレクト、URL の書き換え、レスポンスヘッダーの設定、A/B テストの実行、および国際化ルーティングの処理が含まれます。できないこと:req.bodyの読み取り、Node.jsの組み込みモジュール(fs、path)の使用、データベースへのアクセス、およびfetch以外の外部リクエストの送信。複雑な認証ロジックは、ルートハンドラーで実装することを推奨します。
Q ISRとオンデマンドISRの違いは何ですか?
A ISRはスケジュールされた再検証です。revalidate: 60を設定すると、ページは最大60秒以内に更新されます。オンデマンドISRはイベント駆動型の再読み込みであり、revalidateTag()またはrevalidatePath()を呼び出すとページが即座に更新されるため、コンテンツの変更を直ちに反映させる必要があるシナリオに適しています。これら2つは組み合わせて使用することも可能です。フェイルオーバーとして再検証時間を長く設定しつつ、コンテンツが変更された際にはオンデマンドISRを使用して即座に更新を行うといった使い方ができます。
Q ルートハンドラーでCORS(Cross-Origin Resource Sharing)をどのように処理すればよいですか?
A ルートハンドラーのレスポンス内で、Access-Control-Allow-Origin などのCORSヘッダーを直接設定するだけです。統一されたCORSレスポンスヘッダーオブジェクトを返すユーティリティ関数 corsHeaders() をラップし、Response.json(data, { headers: corsHeaders() }) を使用して各ルートハンドラーに組み込むことができます。フロントエンドとバックエンドが同じドメイン上にある場合(Next.jsのフルスタック展開では通常このケースになります)、CORSを処理する必要はありません。

📖 まとめ


📝 練習問題

  1. プロジェクト内に app/api/products/route.ts を作成し、サンプル製品 API を実装します。GET メソッドは製品リストを返し、POST メソッドは新しい製品を作成します。app/products/page.tsxでは、サーバーコンポーネント fetch を使用してこのAPIを呼び出し、商品リストを表示します。また、revalidate: 30 を設定してISRを実装します。商品データが変更されてから30秒間はページが更新されないことを確認してください。30秒経過後にページを更新すると、新しいデータが表示されます。
  2. /dashboard/* への経路を保護するために middleware.ts を作成します。クッキーに session_token が含まれていない場合は、/login にリダイレクトします。同時に、ログイン済みのユーザーが /login にアクセスした際は、自動的に /dashboard にリダイレクトします。matcher を使用して、必要なパスにのみ一致するようにルーティングを厳密に設定し、_next/static などのリソースパスでミドルウェアが実行されないようにします。
  3. オンデマンドのISR更新メカニズムを実装する:管理者ページにある「キャッシュの更新」ボタンをクリックして、POST /api/revalidatex-revalidate-secretリクエストヘッダーによる認証付き)を呼び出し、revalidateTag('products')を介して商品ページのISRキャッシュを更新する。更新後、ページが直ちに更新されるかどうかを確認し、スケジュールされたISRとオンデマンドのISRの更新速度を比較する。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%