nextjs-data-fetching
Next.jsのApp Routerにおけるデータ取得パターンを提供し、SWRやReact Queryの統合、並列データ取得、ISR(インクリメンタル静的再生成)、再検証戦略、エラーバウンダリをカバーします。Next.jsアプリでのデータ取得実装、サーバー/クライアントフェッチの選択、キャッシュ戦略の設計、ローディングやエラー状態の処理を行う際に活用してください。
description の原文を見る
Provides Next.js App Router data fetching patterns including SWR and React Query integration, parallel data fetching, Incremental Static Regeneration (ISR), revalidation strategies, and error boundaries. Use when implementing data fetching in Next.js applications, choosing between server and client fetching, setting up caching strategies, or handling loading and error states.
SKILL.md 本文
Next.js データフェッチング
概要
Next.js App Router におけるデータフェッチングのパターンを提供します: サーバー側フェッチング、SWR/React Query 統合、ISR、リバリデーション、エラーバウンダリ、ローディング状態に対応しています。
使用場面
- Next.js App Router でのデータフェッチング実装
- Server Components と Client Components の選択
- クライアント側キャッシング用の SWR または React Query の設定
- ISR、時間ベース、またはオンデマンドリバリデーションの設定
- ローディングとエラー状態の処理
- Server Actions を使用したフォーム構築
説明
Server Component フェッチング
非同期 Server Component で直接フェッチします:
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
if (!res.ok) throw new Error('Failed to fetch posts');
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
並列データフェッチング
独立したリクエストに対して Promise.all() を使用します:
async function getDashboardData() {
const [user, posts, analytics] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/analytics').then(r => r.json()),
]);
return { user, posts, analytics };
}
export default async function DashboardPage() {
const { user, posts, analytics } = await getDashboardData();
// ダッシュボードをレンダリング
}
順序的データフェッチング (依存関係がある場合)
async function getUserPosts(userId: string) {
const user = await fetch(`/api/users/${userId}`).then(r => r.json());
const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
return { user, posts };
}
時間ベースリバリデーション (ISR)
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 } // 60秒ごとにリバリデーション
});
return res.json();
}
オンデマンドリバリデーション
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
const tag = request.nextUrl.searchParams.get('tag');
if (tag) {
revalidateTag(tag);
return Response.json({ revalidated: true });
}
return Response.json({ revalidated: false }, { status: 400 });
}
選択的なリバリデーション用にタグを付けます:
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'], revalidate: 3600 }
});
return res.json();
}
キャッシングの無効化
async function getRealTimeData() {
const res = await fetch('https://api.example.com/data', {
cache: 'no-store'
});
return res.json();
}
// または:
export const dynamic = 'force-dynamic';
クライアント側データフェッチング
SWR 統合
インストール: npm install swr
'use client';
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(r => r.json());
export function Posts() {
const { data, error, isLoading } = useSWR('/api/posts', fetcher, {
refreshInterval: 5000,
revalidateOnFocus: true,
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Failed to load posts</div>;
return (
<ul>
{data.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
React Query 統合
インストール: npm install @tanstack/react-query
// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
refetchOnWindowFocus: false,
},
},
}));
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
詳細は react-query.md を参照してください。ミューテーション、楽観的更新、無限クエリ、高度なパターンが含まれています。
エラーバウンダリ
クライアント側データフェッチングをエラーバウンダリでラップして、障害を適切に処理します:
詳細な ErrorBoundary 実装 (基本版、リセットコールバック付き) とデータフェッチングの使用例については error-boundaries.md を参照してください。
Server Actions
キャッシュリバリデーション付きのミューテーション用に Server Actions を使用します:
フォーム検証用の useActionState、エラー処理、キャッシュ無効化を含む完全な例については server-actions.md を参照してください。
ローディング状態
loading.tsx パターン
// app/posts/loading.tsx
export default function PostsLoading() {
return (
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div key={i} className="h-16 bg-gray-200 animate-pulse rounded" />
))}
</div>
);
}
Suspense バウンダリ
// app/posts/page.tsx
import { Suspense } from 'react';
import { PostsList } from './PostsList';
import { PostsSkeleton } from './PostsSkeleton';
export default function PostsPage() {
return (
<div>
<h1>Posts</h1>
<Suspense fallback={<PostsSkeleton />}>
<PostsList />
</Suspense>
</div>
);
}
ベストプラクティス
- Server Components を優先 — より良いパフォーマンスのため Server Components でフェッチします
- 並列フェッチングを使用 — 独立したリクエストに対して
Promise.all()でレイテンシを削減します - 適切なキャッシング戦略を選択:
- 静的データ: 長いリバリデーション間隔
- 動的データ: 短いリバリデーションまたは
cache: 'no-store' - ユーザー固有のデータ: 動的レンダリングを使用します
- エラーを適切に処理 — クライアント側データフェッチングをエラーバウンダリでラップします
- ローディング状態を実装 —
loading.tsxまたは Suspense バウンダリを使用します - SWR/React Query を優先: リアルタイムデータ、ユーザーインタラクション、バックグラウンド更新
- Server Actions を使用: フォーム送信、キャッシュリバリデーションが必要なミューテーション
制約と警告
重要な制約
- Server Components はフック (
useState,useEffect) やクライアント側データフェッチングライブラリを使用できません - Client Components は
'use client'ディレクティブを含める必要があります - Next.js の
fetchAPI は標準 Web fetch を Next.js 固有のキャッシングオプションで拡張しています - Server Actions は
'use server'が必要で、Client Components またはフォームアクションからのみ呼び出すことができます
よくある落とし穴
- ループ内でのフェッチング — Server Components での順序的フェッチを避けます。並列フェッチングを使用してください
- キャッシュ汚染 — ユーザー固有またはパーソナライズされたデータに対して
force-cacheを使用しないでください - メモリリーク — リアルタイムデータを使用する場合、Client Components でサブスクリプションをクリーンアップします
- ハイドレーション不一致 — React Query ハイドレーションでサーバーとクライアントが同じ初期状態をレンダリングするようにしてください
例
例1: ISR を使用したブログ
入力: 投稿をフェッチし、1時間ごとに更新するブログページを作成します。
// app/blog/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
});
return res.json();
}
export default async function BlogPage() {
const posts = await getPosts();
return (
<main>
<h1>Blog Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
);
}
出力: ページはビルド時に静的に生成され、1時間ごとにリバリデーションされます。
例2: 並列フェッチングを使用したダッシュボード
入力: ユーザープロフィール、統計情報、最近のアクティビティを並列で表示するダッシュボードを構築します。
// app/dashboard/page.tsx
async function getDashboardData() {
const [user, stats, activity] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/stats').then(r => r.json()),
fetch('/api/activity').then(r => r.json()),
]);
return { user, stats, activity };
}
export default async function DashboardPage() {
const { user, stats, activity } = await getDashboardData();
return (
<div className="dashboard">
<UserProfile user={user} />
<StatsCards stats={stats} />
<ActivityFeed activity={activity} />
</div>
);
}
出力: 3つのリクエストがすべて同時に実行され、総読み込み時間が最小化されます。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- giuseppe-trisciuoglio
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/giuseppe-trisciuoglio/developer-kit / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。