Chuyển đến nội dung chính

レッスン 10: データのフェッチとキャッシュ

サーバー側のデータ取得、API キャッシュの取得。静的レンダリングと動的レンダリング、ISR。並列データ取得、ウォーターフォール防止。不安定なキャッシュ、再検証。

💻 プログラミング — レッスン 10 レッスン 10: データのフェッチとキャッシュ

React と Next.js: 基本から高度まで

パート 3: Next.js の基礎

xdev.asia

1. サーバー側のデータ取得

// Async Server Component — fetch directly
export default async function PostsPage() {
  const posts = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600 }, // ISR: revalidate every hour
  }).then(res => res.json());

  return (
    <div>
      {posts.map((post: Post) => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  );
}

2. キャッシュ戦略

// Static (cached forever until redeployed)
const data = await fetch('https://api.example.com/data', {
  cache: 'force-cache', // Default behavior
});

// Dynamic (no cache, fresh every request)
const data = await fetch('https://api.example.com/data', {
  cache: 'no-store',
});

// ISR (revalidate after N seconds)
const data = await fetch('https://api.example.com/data', {
  next: { revalidate: 60 }, // Revalidate every 60 seconds
});

// Tag-based revalidation
const data = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] },
});

// Revalidate by tag (from Server Action or Route Handler)
import { revalidateTag, revalidatePath } from 'next/cache';
revalidateTag('posts');       // Invalidate all fetches with 'posts' tag
revalidatePath('/blog');      // Invalidate specific path

3. 並列データフェッチ

// ❌ Waterfall — sequential fetching
export default async function Dashboard() {
  const user = await fetchUser();        // 500ms
  const orders = await fetchOrders();    // 500ms
  const analytics = await fetchAnalytics(); // 500ms
  // Total: 1500ms 😱
}

// ✅ Parallel — concurrent fetching
export default async function Dashboard() {
  const [user, orders, analytics] = await Promise.all([
    fetchUser(),        // 500ms
    fetchOrders(),      // 500ms ← concurrent
    fetchAnalytics(),   // 500ms ← concurrent
  ]);
  // Total: 500ms 🚀
}

// ✅ Streaming — each section loads independently
export default function Dashboard() {
  return (
    <div>
      <Suspense fallback={<Skeleton />}>
        <UserInfo />
      </Suspense>
      <Suspense fallback={<Skeleton />}>
        <OrderList />
      </Suspense>
      <Suspense fallback={<Skeleton />}>
        <Analytics />
      </Suspense>
    </div>
  );
}

4. データベースへの直接アクセス

import { db } from '@/lib/db';

// Truy vấn database trực tiếp trong Server Component
async function UserList() {
  const users = await db.user.findMany({
    where: { isActive: true },
    orderBy: { createdAt: 'desc' },
    take: 10,
  });

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

5.不安定なキャッシュ

import { unstable_cache } from 'next/cache';

const getCachedUser = unstable_cache(
  async (userId: string) => {
    return db.user.findUnique({ where: { id: userId } });
  },
  ['user'], // Cache key prefix
  {
    revalidate: 3600, // 1 hour
    tags: ['user'],   // Tag for manual revalidation
  }
);

// Usage in Server Component
export default async function Profile({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const user = await getCachedUser(id);

  return <div>{user?.name}</div>;
}

6. 静的レンダリングと動的レンダリング

レンダリングいつキャッシュ
静的動的データや Cookie/ヘッダーはありませんビルド時間、CDN
ダイナミッククッキー()、ヘッダー()、searchParamsリクエスト時間
ISR静的 + 再検証ビルド時間 + 定期的な更新
// Force dynamic rendering
export const dynamic = 'force-dynamic';
// Or: export const revalidate = 0;

// Force static
export const dynamic = 'force-static';

次の記事: サーバーアクションとミューテーション — 「サーバーを使用」、フォームアクション、楽観的な更新。