NuxtMiddleTechnical

Что такое утилита $fetch и чем она отличается от нативного fetch?

$fetch — это глобальный алиас ofetch в Nuxt 3: автоматически парсит JSON, выбрасывает типизированные ошибки и поддерживает интерцепторы, в отличие от нативного fetch. Для SSR-компонентов его нужно оборачивать в useAsyncData, иначе запрос выполнится дважды.

Что такое $fetch

$fetch в Nuxt 3 — это глобальный алиас для ofetch, HTTP-клиента от команды UnJS. Он автоматически доступен в любом месте приложения без импорта. В отличие от нативного fetch, ofetch предоставляет:

  • Автоматический парсинг JSON (и FormData, и text — в зависимости от Content-Type).
  • Умную обработку ошибок: выбрасывает объект FetchError с полями status, statusText, data вместо необходимости проверять response.ok.
  • Интерцепторы: onRequest, onResponse, onRequestError, onResponseError.
  • Поддержку BaseURL через useRequestFetch и контекст SSR.

Сравнение с нативным fetch

// Нативный fetch
const res = await fetch('/api/users')
if (!res.ok) throw new Error('HTTP error')
const data = await res.json()

// $fetch в Nuxt
const data = await $fetch('/api/users')
// data уже распарсен, ошибки выбрасываются автоматически

SSR-специфика: почему не вызывать $fetch напрямую в useAsyncData

Когда компонент рендерится на сервере, вызов $fetch('/api/...') напрямую приводит к HTTP-запросу на тот же сервер — это лишнее сетевое взаимодействие. Вместо этого Nuxt рекомендует использовать useAsyncData + $fetch или просто useFetch:

// Неоптимально на SSR (двойной вызов при гидратации)
const data = await $fetch('/api/posts')

// Правильно: один вызов на сервере, результат передаётся клиенту
const { data } = await useAsyncData('posts', () => $fetch('/api/posts'))

// Или через useFetch (обёртка над useAsyncData + $fetch)
const { data } = await useFetch('/api/posts')

Создание кастомного экземпляра

Для добавления глобальных заголовков (Authorization, Accept-Language) используйте $fetch.create():

// composables/useApiFetch.ts
export const useApiFetch = () => {
  const { token } = useAuth()
  return $fetch.create({
    baseURL: useRuntimeConfig().public.apiBase,
    headers: {
      Authorization: `Bearer ${token.value}`
    },
    onResponseError({ response }) {
      if (response.status === 401) {
        navigateTo('/login')
      }
    }
  })
}

Интерцепторы

const data = await $fetch('/api/data', {
  onRequest({ options }) {
    options.headers = {
      ...options.headers,
      'X-Request-ID': crypto.randomUUID()
    }
  },
  onResponseError({ response }) {
    console.error('API error:', response._data)
  }
})

Server-side: useRequestFetch

Внутри server-хуков и middleware Nuxt автоматически передаёт cookie запроса через useRequestFetch(), что необходимо для аутентифицированных server-side вызовов:

// plugins/api.server.ts
export default defineNuxtPlugin(() => {
  const fetchWithCookies = useRequestFetch()
  return {
    provide: {
      api: fetchWithCookies
    }
  }
})

Подводные камни

  • Двойной запрос при SSR — использование $fetch вне useAsyncData/useFetch в компоненте приводит к выполнению запроса на сервере и повторно на клиенте при гидратации.
  • FetchError.data — при ошибке ответ сервера доступен в error.data, а не в error.message; начинающие ловят ошибку и теряют тело ответа.
  • BaseURL на сервере — $fetch с относительным путём на сервере не знает хост; нужно либо задать baseURL, либо использовать useRequestURL().
  • Куки не пробрасываются автоматически — при SSR $fetch не копирует cookie клиента в исходящий запрос; для этого используйте useRequestFetch().
  • Отсутствие встроенного кеша — $fetch не кеширует ответы сам по себе; кеширование обеспечивает useAsyncData через ключ.
  • Интерцепторы не наследуются — если создали кастомный экземпляр через $fetch.create(), интерцепторы работают только для него, не для глобального $fetch.
  • Таймаут не задан по умолчанию — долгий запрос будет висеть без ошибки; явно добавляйте timeout в опции.

Common mistakes

  • Путать $fetch с похожим API из соседнего фреймворка.
  • Не объяснять, где код выполняется: сервер, клиент, build step или runtime.
  • Игнорировать влияние на hydration, cache, bundle size или безопасность.

What the interviewer is testing

  • Точно объясняет назначение механизма «$fetch».
  • Показывает корректный минимальный пример без выдуманных API.
  • Называет ограничения, failure modes и production-компромиссы.

Sources

Related topics