😸

なぜ Next.js で axios を入れず、fetch を薄くラップしたか

に公開

要約

  • 前提: Next.js 13+(App Router)、TypeScript、SSR/RSC を併用
  • 判断: 外部ライブラリの axios は導入せず、組み込みの fetch を小さくラップして利用
  • 理由: Next.js の実行・キャッシュモデルに fetch が自然に統合。私たちのユースケースでは 必要十分
  • 効果: 依存・設定を最小化しつつ、共通ヘッダー/エラー処理/型をひとつのレイヤーに集約

対象読者 / 想定環境

  • 対象: Next.js(App Router)で SSR/RSC を使う小~中規模の Web アプリ
  • 環境: Next.js 13 以上、TypeScript、Node.js 18+
  • ゴール: 余計な依存を増やさず 一貫したリクエスト層 を整える

背景と判断の軸

多くのプロジェクトでは慣例的に axios を入れます。しかし、私たちの要件(シンプルな CRUD が中心・複雑なインターセプタ連鎖は不要)では、**「組み込み fetch + 薄いラッパ」**のほうが合理的でした。

1) Next.js のモデルに自然に乗る

  • App Router/RSC では サーバー側の fetch が自動キャッシュ されるなど、実行時最適化と密接
  • fetch(url, { cache: 'no-store' }) など 公式オプション で挙動を制御
    → ランタイムに近い層へ依存を増やさず、Next.js 本来の最適化を活かせる

2) “いま必要な機能” に絞れる

  • axios のインターセプタやタイムアウト等は便利だが、私たちのケースでは fetch + 小さなユーティリティ で十分
  • 余分な依存や設定を足さず、シンプルさ可読性 を優先

3) 両環境で一貫して使える

  • サーバー/クライアント双方で fetch が使え、API は一つ で済む
  • 環境分岐が減り、実装・レビューコスト を抑制

実装:customFetch(最小限のラッパ)

目的は「共通ヘッダー・エラーハンドリング・返却型の統一」。大きくし過ぎず、“必要になったら足す” 方針。

// lib/custom-fetch.ts
export async function customFetch<T>(
  input: RequestInfo,
  init?: RequestInit
): Promise<T> {
  const res = await fetch(input, {
    ...init,
    headers: {
      'Content-Type': 'application/json',
      ...(init?.headers || {}),
    },
  });

  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`[${res.status}] ${errorText}`);
  }

  return res.json() as Promise<T>;
}

**ポイント**

* **共通ヘッダー** を一箇所で付与
* **エラー整形** を統一(ログ/トーストの起点をここに集約可能)
* **ジェネリクス** により返却型を明示し、呼び出し側の推論を補助

---

## 使用例

```ts
import { customFetch } from '@/lib/custom-fetch';

type User = { id: string; name: string };

export const getUser = async () => {
  const data = await customFetch<User>('/api/user');
  return data;
};
  • 呼び出し側は 型の意図 が読みやすく、テスト観点でも扱いやすい
  • 再試行(retry)や計測(metrics)を足す場合も 注入ポイントは一箇所

環境ごとの分岐(必要なら)

const isServer = typeof window === 'undefined';
const baseUrl = isServer ? process.env.API_URL : '';

await fetch(baseUrl + '/api/endpoint');

まずは最小構成で始め、要件が増えたら 小さく育てる 方針が破綻しにくいと感じました。


どこまでを fetch で賄い、どこから拡張するか

  • まずは fetch:キャッシュ/再検証や RSC の挙動まで含め、Next.js との親和性が高い

  • 必要に応じて拡張

    • 再試行・バックオフ、署名、観測(OpenTelemetry 等)は customFetch に積み上げる
    • それでも不足する複雑要件(高度なインターセプトや独自トランスポート)が出た時点で専用ライブラリ導入を検討

「いま必要な最小」を守ると、依存の寿命保守コスト の見通しが良くなる


まとめ

  • Next.js の強み(fetch と実行モデルの統合)を活かすため、axios は入れず 薄い customFetch を採用
  • 小さなラッパでも 共通化(ヘッダー/エラー/型) の恩恵は十分
  • シンプルさと拡張性 を両立しやすい運用パターン

付記(Disclosure)

本記事は 技術共有 を目的としています。
個人的にリンク管理ツール Link Dropper を運用しています。関心があればこちらをご覧ください(簡単な紹介のみ)。
👉 https://link-dropper.com

※ 記事本体は特定サービスの宣伝を主目的としていません。内容の正確性・再現性に関するご指摘は歓迎します。


運用メモ(所感)

  • レビュー容易性customFetch の差分は小さく、PR で議論しやすい
  • ランタイム差異の吸収:Node/ブラウザ差はラッパ内で吸収し、呼び出し側は意識不要
  • 監視の入口:レスポンス時間・失敗率の計測追加も 一箇所で完結

仕様の更新に追従するため、キャッシュ/再検証や RSC の挙動は Next.js 公式ドキュメントを都度確認するのがおすすめです。

Discussion