brand logo

ドキュメント

Nuxt におけるカスタム useFetch

Nuxt で外部 API を呼び出すためのカスタムフェッチャーを作成する方法。

Nuxt を使用する際、フロントエンドを作成し外部 API をフェッチすることがあるかもしれません。その際、API からフェッチするためのデフォルトオプションを設定したい場合があります。

$fetch ユーティリティ関数(useFetch コンポーザブルで使用される)は、意図的にグローバルに設定可能ではありません。これは、アプリケーション全体でフェッチの動作が一貫していることを保証し、他の統合(モジュールなど)が $fetch のようなコアユーティリティの動作に依存できるようにするためです。

しかし、Nuxt は API 用のカスタムフェッチャー(複数の API を呼び出す場合は複数のフェッチャー)を作成する方法を提供しています。

カスタム $fetch

Nuxt プラグインを使用してカスタム $fetch インスタンスを作成しましょう。

$fetchofetch の設定済みインスタンスであり、Nuxt サーバーのベース URL を追加したり、SSR 中に直接関数呼び出しをサポートしています(HTTP ラウンドトリップを回避)。

ここでは以下のように仮定します:

  • メイン API は https://api.nuxt.com です
  • JWT トークンを nuxt-auth-utils を使用してセッションに保存しています
  • API が 401 ステータスコードで応答した場合、ユーザーを /login ページにリダイレクトします
plugins/api.ts
export default defineNuxtPlugin((nuxtApp) => {
  const { session } = useUserSession()

  const api = $fetch.create({
    baseURL: 'https://api.nuxt.com',
    onRequest({ request, options, error }) {
      if (session.value?.token) {
        // これは ofetch >= 1.4.0 に依存しています - ロックファイルを更新する必要があるかもしれません
        options.headers.set('Authorization', `Bearer ${session.value?.token}`)
      }
    },
    async onResponseError({ response }) {
      if (response.status === 401) {
        await nuxtApp.runWithContext(() => navigateTo('/login'))
      }
    }
  })

  // useNuxtApp().$api で使用できるように公開
  return {
    provide: {
      api
    }
  }
})

この Nuxt プラグインを使用すると、$apiuseNuxtApp() から公開され、Vue コンポーネントから直接 API 呼び出しが可能になります:

app.vue
const { $api } = useNuxtApp()
const { data: modules } = await useAsyncData('modules', () => $api('/modules'))

useAsyncData でラップすることで、サーバーサイドレンダリング時のデータの二重フェッチを回避します(サーバーとクライアントのハイドレーション時)。

カスタム useFetch/useAsyncData

$api に必要なロジックが含まれたので、useAsyncData + $api の使用を置き換えるための useAPI コンポーザブルを作成しましょう:

composables/useAPI.ts
import type { UseFetchOptions } from 'nuxt/app'

export function useAPI<T>(
  url: string | (() => string),
  options?: UseFetchOptions<T>,
) {
  return useFetch(url, {
    ...options,
    $fetch: useNuxtApp().$api as typeof $fetch
  })
}

新しいコンポーザブルを使用して、きれいでシンプルなコンポーネントを作成しましょう:

app.vue
const { data: modules } = await useAPI('/modules')

返されるエラーのタイプをカスタマイズしたい場合も可能です:

import type { FetchError } from 'ofetch'
import type { UseFetchOptions } from 'nuxt/app'

interface CustomError {
  message: string
  statusCode: number
}

export function useAPI<T>(
  url: string | (() => string),
  options?: UseFetchOptions<T>,
) {
  return useFetch<T, FetchError<CustomError>>(url, {
    ...options,
    $fetch: useNuxtApp().$api
  })
}

この例はカスタム useFetch の使用方法を示していますが、カスタム useAsyncData の場合も同じ構造です。

サンプルコードの編集とプレビューexamples > advanced > use-custom-fetch-composable

カスタムフェッチャーを作成するためのよりクリーンな方法を見つけるために現在議論中です。詳細は https://github.com/nuxt/nuxt/issues/14736 を参照してください。

tips

このセクションは公式ドキュメントの翻訳ではなく、本サイト独自の補足記事です。

Nuxtにおけるカスタムフェッチャーの活用ポイント

Nuxtで外部APIを扱う際、単にuseFetchuseAsyncDataを使うだけでなく、API呼び出しの共通処理や認証トークンの付与、エラーハンドリングなどを一元管理できるカスタムフェッチャーを作成することが多くなっています。
これにより、コードの重複を減らし、保守性を高めるだけでなく、サーバーサイドレンダリング(SSR)やクライアントサイドレンダリング(CSR)での挙動を安定させることが可能です。

本記事では、Nuxt公式ドキュメントのカスタムuseFetchに関する内容を補足し、実務での具体的な使い方や注意点を丁寧に解説します。


まず結論:カスタムフェッチャーのポイント

  • 共通設定の一元管理
    APIのベースURLや認証ヘッダー、エラーハンドリングをプラグインでまとめて設定可能。

  • useFetchuseAsyncDataとの組み合わせで効率化
    カスタムフェッチャーを使うことで、API呼び出しコードをシンプルに保てる。

  • SSRとCSRの両方で安定した動作を実現
    Nuxtの$fetchはSSR時にHTTPリクエストを省略できるためパフォーマンス向上に寄与。

  • 認証トークンの自動付与やリダイレクト処理も組み込みやすい
    401エラー時のログインページへのリダイレクトなど、共通の挙動を一括管理可能。

  • 使いどころを見極めることが重要
    単純なAPI呼び出しには不要な場合もあり、過剰な抽象化はかえって複雑化するリスクあり。


いつ使うべきか?使わない方がよいケース

カスタムフェッチャーを使うべきケース

  • 複数のAPIエンドポイントを扱い、共通の認証やエラーハンドリングが必要なとき
  • API呼び出しの設定(ベースURLやヘッダーなど)を統一して管理したいとき
  • SSRとCSRの両方で同じAPI呼び出しロジックを使い、二重フェッチを防ぎたいとき
  • アプリケーション全体でAPI呼び出しの挙動を一貫させたいとき

カスタムフェッチャーを使わない方がよいケース

  • 単純なAPI呼び出しや一度きりのフェッチで、共通処理が不要なとき
  • APIの仕様が頻繁に変わり、カスタムフェッチャーのメンテナンスコストが高くなる場合
  • 小規模なプロジェクトで抽象化による複雑化を避けたいとき

実務でよくあるユースケースとサンプルコード

1. 認証トークンを自動付与するAPIクライアント

ユーザーのJWTトークンをセッションから取得し、APIリクエストのヘッダーに自動でセットします。
これにより、各コンポーネントでトークンを意識せずにAPI呼び出しが可能です。

// plugins/api.ts
export default defineNuxtPlugin((nuxtApp) => {
  const { session } = useUserSession()

  const api = $fetch.create({
    baseURL: 'https://api.example.com',
    onRequest({ options }) {
      if (session.value?.token) {
        options.headers.set('Authorization', `Bearer ${session.value.token}`)
      }
    },
    async onResponseError({ response }) {
      if (response.status === 401) {
        await nuxtApp.runWithContext(() => navigateTo('/login'))
      }
    }
  })

  return {
    provide: {
      api
    }
  }
})

2. カスタムuseFetchラッパーでAPI呼び出しを簡潔に

上記の$apiを使い、useFetchのオプションに組み込んだラッパー関数を作成。
これにより、API呼び出しがシンプルに書けます。

// composables/useAPI.ts
import type { UseFetchOptions } from 'nuxt/app'

export function useAPI<T>(
  url: string | (() => string),
  options?: UseFetchOptions<T>,
) {
  return useFetch(url, {
    ...options,
    $fetch: useNuxtApp().$api as typeof $fetch
  })
}
const { data: userData } = await useAPI('/user/profile')

3. エラーハンドリングをカスタマイズした例

APIから返るエラーの型を独自に定義し、型安全に扱うことも可能です。

import type { FetchError } from 'ofetch'
import type { UseFetchOptions } from 'nuxt/app'

interface ApiError {
  message: string
  statusCode: number
}

export function useAPI<T>(
  url: string | (() => string),
  options?: UseFetchOptions<T>,
) {
  return useFetch<T, FetchError<ApiError>>(url, {
    ...options,
    $fetch: useNuxtApp().$api
  })
}

よくある落とし穴・注意点

SSRとCSRでの二重フェッチ問題

useAsyncDatauseFetchはSSR時にデータを取得し、クライアント側で再度フェッチされることを防ぐ仕組みがあります。
カスタムフェッチャーを使う際も、この仕組みを活かすためにuseAsyncDataでラップするのが望ましいです。

Hydrationの不整合に注意

APIレスポンスがSSRとCSRで異なる場合、Hydrationエラーが発生することがあります。
APIのレスポンスはできるだけ一貫性を保ち、クライアント側でのみ変化するデータは別途管理しましょう。

パフォーマンスへの影響

カスタムフェッチャーで複雑な処理や重いロジックを入れると、リクエストのたびに処理コストが増加します。
必要最低限の処理に留め、キャッシュやプリフェッチなどNuxtの機能も活用しましょう。

$fetchのバージョン依存

$fetch.createのオプションや挙動はofetchのバージョンに依存します。
プロジェクトの依存関係を適切に管理し、バージョンアップ時は動作確認を必ず行いましょう。


まとめ

Nuxtでのカスタムフェッチャーは、API呼び出しの共通処理を一元化し、認証やエラーハンドリングを統一できる強力な手法です。
SSRとCSRの両方で安定した動作を実現し、コードの保守性も向上します。

ただし、過剰な抽象化や複雑なロジックの混入は逆効果になるため、使うべきケースを見極めて導入しましょう。
実務でのユースケースを踏まえた設計と、SSR/CSRの特性を理解した上で活用することが成功の鍵です。


カスタムフェッチャーを作成したら、まずは小さなAPIエンドポイントで試し、動作やパフォーマンスを確認しながら徐々に拡張していくことをおすすめします。