0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

要約

  • 前提: 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 公式ドキュメントを都度確認するのがおすすめです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?