データ取得
Server Componentsでのデータフェッチング
サーバーコンポーネントでのfetch
async コンポーネント
サーバー上で直接データ取得
typescript
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/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>
);
}プレビュー
// Server Component なので
// - APIキーが漏れない
// - DBに直接アクセス可能
// - 初回表示が速い
キャッシュとrevalidate
キャッシュの制御
データの鮮度を管理
typescript
// デフォルト: キャッシュされる
const res = await fetch('https://...');
// キャッシュしない(毎回リクエスト)
const res = await fetch('https://...', {
cache: 'no-store'
});
// 一定時間後に再検証
const res = await fetch('https://...', {
next: { revalidate: 60 } // 60秒後に再検証
});
// ページ全体のrevalidate
export const revalidate = 60;プレビュー
'force-cache' // キャッシュ優先
'no-store' // 常に新しいデータ
revalidate: 60 // 60秒ごと更新
並列データ取得
Promise.all で高速化
複数のリクエストを同時実行
typescript
// app/dashboard/page.tsx
async function getUser() {
const res = await fetch('https://api.example.com/user');
return res.json();
}
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
return res.json();
}
export default async function Dashboard() {
// 並列で取得(速い!)
const [user, posts] = await Promise.all([
getUser(),
getPosts()
]);
return (
<div>
<h1>{user.name}さんのダッシュボード</h1>
<PostList posts={posts} />
</div>
);
}プレビュー
// 順番に取得: 2秒 + 2秒 = 4秒
// 並列で取得: max(2秒, 2秒) = 2秒
動的パラメータを使った取得
paramsを使ったデータ取得
URLパラメータでAPIを呼び出す
typescript
// app/blog/[slug]/page.tsx
interface Props {
params: { slug: string }
}
async function getPost(slug: string) {
const res = await fetch(
`https://api.example.com/posts/${slug}`
);
if (!res.ok) {
notFound(); // 404ページを表示
}
return res.json();
}
export default async function BlogPost({ params }: Props) {
const post = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}プレビュー
/blog/hello-world
// → getPost("hello-world")
// → API: /posts/hello-world
ローディング状態
loading.tsx
自動的にローディングUIを表示
typescript
// app/posts/loading.tsx
export default function Loading() {
return (
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-4"></div>
<div className="h-4 bg-gray-200 rounded w-1/2 mb-4"></div>
<div className="h-4 bg-gray-200 rounded w-5/6"></div>
</div>
);
}
// app/posts/page.tsx はそのまま
// データ取得中は自動的にloading.tsxが表示されるプレビュー
Suspenseで部分的にローディング
コンポーネント単位でローディングを制御
typescript
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<h1>ダッシュボード</h1>
{/* このコンポーネントだけローディング */}
<Suspense fallback={<p>投稿を読み込み中...</p>}>
<PostList />
</Suspense>
{/* これはすぐ表示される */}
<Sidebar />
</div>
);
}プレビュー
// Suspenseで囲んだ部分だけ
// 待っている間 fallback を表示
エラーハンドリング
error.tsx
エラー時のフォールバックUI
typescript
// app/posts/error.tsx
'use client';
interface Props {
error: Error;
reset: () => void;
}
export default function Error({ error, reset }: Props) {
return (
<div>
<h2>エラーが発生しました</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>
もう一度試す
</button>
</div>
);
}プレビュー
エラーが発生しました
データの取得に失敗しました