データフェッチ
Next.jsでのデータ取得方法を学びます。サーバーコンポーネントとクライアントコンポーネントの使い分けがポイントです。
サーバーコンポーネントでのフェッチ
app/posts/page.tsx(推奨)
// サーバーコンポーネント(デフォルト)
// "use client" を書かなければサーバーコンポーネント
type Post = {
id: number;
title: string;
content: string;
};
async function getPosts(): Promise<Post[]> {
const res = await fetch(`${process.env.API_URL}/posts`, {
// キャッシュ戦略
cache: "no-store", // 常に最新を取得
// または
// next: { revalidate: 60 } // 60秒ごとに再検証
});
if (!res.ok) {
throw new Error("Failed to fetch posts");
}
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<div className="container mx-auto px-6 py-8">
<h1 className="text-2xl font-bold mb-6">記事一覧</h1>
<ul className="space-y-4">
{posts.map((post) => (
<li key={post.id} className="bg-white p-4 rounded shadow">
<h2 className="font-bold">{post.title}</h2>
<p className="text-gray-600">{post.content.slice(0, 100)}...</p>
</li>
))}
</ul>
</div>
);
}
サーバーコンポーネントのメリット
- ・データ取得がサーバー側で完結(セキュア)
- ・初期表示が速い(クライアントJS不要)
- ・SEOに有利
クライアントコンポーネント + SWR
インストール
npm install swr
components/PostList.tsx
"use client";
import useSWR from "swr";
type Post = {
id: number;
title: string;
content: string;
};
const fetcher = (url: string) => fetch(url).then((res) => res.json());
export function PostList() {
const { data, error, isLoading, mutate } = useSWR<Post[]>(
`${process.env.NEXT_PUBLIC_API_URL}/posts`,
fetcher
);
if (isLoading) return <div>読み込み中...</div>;
if (error) return <div>エラーが発生しました</div>;
if (!data) return null;
return (
<div>
<button
onClick={() => mutate()} // 手動で再取得
className="mb-4 text-blue-600"
>
更新
</button>
<ul className="space-y-4">
{data.map((post) => (
<li key={post.id} className="bg-white p-4 rounded shadow">
<h2 className="font-bold">{post.title}</h2>
</li>
))}
</ul>
</div>
);
}
SWRの機能
- ・自動キャッシュ
- ・フォーカス時の自動再検証
- ・エラー時の自動リトライ
- ・リアルタイム更新
認証付きフェッチ
"use client";
import useSWR from "swr";
import { useAuth } from "@/contexts/AuthContext";
export function MyPosts() {
const { token } = useAuth();
const fetcher = (url: string) =>
fetch(url, {
headers: { Authorization: `Bearer ${token}` },
}).then((res) => {
if (!res.ok) throw new Error("Fetch error");
return res.json();
});
// tokenがある時だけフェッチ(nullを渡すとフェッチしない)
const { data, error, isLoading } = useSWR(
token ? `${process.env.NEXT_PUBLIC_API_URL}/posts/mine` : null,
fetcher
);
if (!token) return <div>ログインしてください</div>;
if (isLoading) return <div>読み込み中...</div>;
if (error) return <div>エラーが発生しました</div>;
return (
<ul>
{data?.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
キャッシュ戦略
| 設定 | 動作 | 用途 |
|---|---|---|
| cache: "force-cache" | キャッシュを優先 | 静的コンテンツ |
| cache: "no-store" | 毎回取得 | リアルタイムデータ |
| next: { revalidate: 60 } | 60秒ごとに再検証 | ある程度新しさが必要 |
エラー処理(error.tsx)
app/posts/error.tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div className="text-center py-8">
<h2 className="text-xl font-bold text-red-600 mb-4">
エラーが発生しました
</h2>
<p className="text-gray-600 mb-4">{error.message}</p>
<button
onClick={reset}
className="bg-blue-600 text-white px-4 py-2 rounded"
>
もう一度試す
</button>
</div>
);
}
まとめ
- ✓ サーバーコンポーネントでの取得が基本(SEO、セキュリティ)
- ✓ インタラクティブな更新が必要ならSWR
- ✓ 適切なキャッシュ戦略を選択
- ✓ error.tsxでエラー境界を設定