Nuxt에서 커스텀 useFetch

Nuxt에서 외부 API를 호출하기 위한 커스텀 fetcher를 만드는 방법.

Nuxt를 사용할 때, 프론트엔드를 만들면서 외부 API를 가져오고, API에서 가져올 때 기본 옵션을 설정하고 싶을 수 있습니다.

$fetch 유틸리티 함수(이는 useFetch 컴포저블에서 사용됨)는 의도적으로 전역적으로 설정할 수 없게 되어 있습니다. 이는 애플리케이션 전반에서 가져오기 동작이 일관되게 유지되고, 다른 통합(예: 모듈)에서도 $fetch와 같은 핵심 유틸리티의 동작에 의존할 수 있도록 하는 것이 중요하기 때문입니다.

하지만, Nuxt는 API를 위한 커스텀 fetcher(여러 API를 호출해야 한다면 여러 fetcher도 가능)를 만드는 방법을 제공합니다.

커스텀 $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에 의존합니다 - lockfile을 새로고침해야 할 수 있습니다.
        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
<script setup>
const { $api } = useNuxtApp()
const { data: modules } = await useAsyncData('modules', () => $api('/modules'))
</script>
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
<script setup>
const { data: modules } = await useAPI('/modules')
</script>

반환되는 에러의 타입을 커스터마이즈하고 싶다면, 다음과 같이 할 수 있습니다:

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도 사용할 수 있습니다.
현재 더 깔끔하게 커스텀 fetcher를 만들 수 있는 방법을 논의 중입니다. 자세한 내용은 https://github.com/nuxt/nuxt/issues/14736 를 참고하세요.