😀

Next.js × useSWRで手軽にデータフェッチ

に公開

こんにちは。大壁です。
今回はNext.jsuseSWRを使って、安全で効率的なデータフェッチを実装していきます。
僕自身も手を動かしながら進めるハンズオン形式です。

1. はじめに

useSWRVercelが開発しているReact Hooksライブラリで、
キャッシュ・再検証・リアルタイム更新 を簡単に実装できます。

useSWRは何回か使ったことありましたが、Vercelのライブラリなのは初めて知りました。

この記事のゴール:

  • Next.js に useSWR を導入
  • API Route からデータを取得
  • 危険な useEffect + useState のデータフェッチから卒業

では、進めていきましょう

2. なぜuseEffectでのデータフェッチは危険なのか?

まず、なぜuseEffectでのデータフェッチが危険なのかについて、おさらいしましょう。
僕自身も詳しく理解していなかったので、改めて学んでみます。

React初学者がやりがちなパターンは以下です。(今回はchatGPTにまとめてもらいました。)
今回は問題点を分かりやすくするために敢えてstateを依存配列に入れています。

import { useEffect, useState } from 'react';

export default function BadExample() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => {
        setUsers(data);
        setLoading(false);
      });
  }, [users]);

  if (loading) return <div>読み込み中...</div>;
  return <pre>{JSON.stringify(users, null, 2)}</pre>;
}

問題点

1. 無限ループの危険
依存配列の指定をミスると、setState → 再レンダー → useEffect再実行のループに。

2. 同時リクエストの競合
古いリクエストが後から返ってきて最新データを上書きする可能性。

3. キャッシュがない
ページを行き来するたびに毎回APIアクセス。

4. エラー処理・リトライの実装負担
すべて手作業で書かないといけない。

3. useSWRを使うメリット

useSWRを使うと、これらの問題を解決できます。

import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(res => res.json());

export default function GoodExample() {
  const { data, error, isLoading } = useSWR('/api/users', fetcher);

  if (isLoading) return <div>読み込み中...</div>;
  if (error) return <div>エラーが発生しました</div>;

  return (
    <ul>
      {data.map((user: { id: number; name: string }) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

メリット

  • 無限ループ防止:内部で依存関係を管理
  • 最新データのみ反映:古いレスポンスは自動で破棄
  • キャッシュ内蔵:ページ戻り時も即表示
  • エラー・ローディング管理:変数1つで判定
  • リアルタイム更新簡単:mutate() で即再取得、refreshIntervalで自動更新

こう比べてみると使わない手はないですね

4. ハンズオン — Next.jsで実装

では、次にハンズオンで解説してきます。

プロジェクト作成

npx create-next-app@latest nextjs-swr-demo
cd nextjs-swr-demo
npm install swr

API Route作成

/src/app/api/users/route.ts

import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json([
    { id: 1, name: 'オカルン', description: 'オカルトマニアの男子高生。幽霊は信じてないけど、宇宙人は信じている。ターボババアにかけられた呪いを解くべく追いかけっこをするはめに。' },
    { id: 2, name: 'モモ', description: '霊媒師の家系の女子高生。硬派な男性がタイプ。宇宙人は信じてないけど、幽霊は信じている。セルポ星人にさらわれ、秘めた超能力に目覚める。' },
    { id: 3, name: 'アイラ', description: '校内の人気美少女。“あるもの”を拾ったことで能力が開花。超能力を持つモモを悪魔と勘違いする。' },
    { id: 4, name: 'ジジ', description: 'モモの幼馴染で初恋相手。イケメンで背が高いが、発言・行動がチャラい。 引っ越し先での怪異のため、星子を頼ってやってきた。' },
  ]);
}

useSWRで取得

/src/app/page.tsx

'use client';

import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(res => res.json());

export default function Home() {
  const { data, error, isLoading } = useSWR('/api/users', fetcher);

  if (isLoading) return <div>読み込み中...</div>;
  if (error) return <div>エラーが発生しました</div>;

  return (
    <main className="container mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">ユーザー 一覧</h1>
      <ul className="bg-gray-100 shadow-md rounded-lg p-4">
        {data.map((user: { id: number; name: string; description: string }) => (
          <li key={user.id} className="border-b border-gray-200 py-2">
            <h2 className="font-bold mb-2">{user.name}</h2>
            <p className="text-gray-600">{user.description}</p>
          </li>
        ))}
      </ul>
    </main>
  );
}

こちらで簡単に取得して表示することができます。
また、refreshIntervalで5秒後に自動更新させることも可能です。

const { data, error, isLoading } = useSWR('/api/users', fetcher, {
  refreshInterval: 5000, // 5秒ごとに再取得
});

6. まとめ

まとめになります。

  • useEffectでのデータフェッチは依存配列や競合処理のミスで危険
  • useSWRはキャッシュ・再検証・エラー処理が内蔵され安全
  • 簡単なコードで安定したデータ取得ができる

改めて学ぶと結構簡単だったので、僕も今後の実装でも取り入れてみようと思います。
それでは、また👋

Discussion