Back

Получение данных на стороне сервера в Nuxt

Получение данных на стороне сервера в Nuxt

Если вы разрабатываете приложение на Nuxt и задаетесь вопросом, почему данные загружаются дважды — или почему компоненты с одинаковым ключом ведут себя неожиданно — вы не одиноки. SSR-загрузка данных в Nuxt имеет специфические правила, которые ставят в тупик даже опытных разработчиков.

Эта статья объясняет, как работают useAsyncData и useFetch в Nuxt 4, охватывая гидратацию payload, поведение при навигации, управление ключами и подводные камни, вызывающие наибольшую путаницу.

Ключевые выводы

  • Nuxt выполняет useFetch и useAsyncData на сервере, сериализует ответы в HTML payload и выполняет гидратацию на клиенте без повторной загрузки
  • Компоненты с одинаковым ключом разделяют идентичное реактивное состояние — используйте уникальные ключи для независимых экземпляров данных
  • В текущих релизах Nuxt 4 определенные опции (handler, transform, pick, getCachedData, default, deep) должны совпадать во всех вызовах с общим ключом
  • Используйте useFetch или useAsyncData для SSR-безопасной загрузки; оставьте $fetch для обработчиков событий и клиентского кода

Как Nuxt выполняет загрузку данных на стороне сервера

Когда вы вызываете useFetch или useAsyncData на странице или в компоненте, Nuxt выполняет эту загрузку на сервере во время первоначального запроса. Сервер сериализует ответ в payload, встроенный в HTML. Когда клиент выполняет гидратацию, он читает этот payload вместо повторной загрузки — исключая дублирующие сетевые запросы.

const { data } = await useFetch('/api/products')

Эта единственная строка выполняется на сервере, встраивает результат в страницу и бесшовно гидратируется на клиенте. Никакой двойной загрузки. Никакого несоответствия при гидратации.

Блокирующая и ленивая загрузка

По умолчанию Nuxt блокирует навигацию до завершения загрузки данных с await. Это гарантирует, что ваша страница отрендерится с уже доступными данными.

Для клиентской навигации вы можете выбрать ленивую загрузку:

const { data, status } = useLazyFetch('/api/comments')

При ленивой загрузке навигация по умолчанию не блокируется, пока загрузка выполняется в фоновом режиме. Вам нужно будет обработать состояние загрузки в вашем шаблоне, используя ref status.

Понимание ключей загрузки данных в Nuxt

Ключи являются центральными в том, как Nuxt кэширует и дедуплицирует запросы. Каждый вызов useFetch использует URL в качестве ключа по умолчанию. Для useAsyncData вы предоставляете ключ явно или позволяете Nuxt сгенерировать детерминированный ключ за вас.

Вот критически важная часть: компоненты с одинаковым ключом разделяют одно и то же состояние. Это включает refs data, error, status и pending.

// Компонент A
const { data } = await useAsyncData('users', () => $fetch('/api/users'))

// Компонент B - разделяет состояние с компонентом A
const { data } = await useAsyncData('users', () => $fetch('/api/users'))

Оба компонента получают идентичные реактивные refs. Измените один, и другой отразит это изменение.

Правила согласованности ключей

В текущих версиях Nuxt 4 Nuxt обеспечивает согласованность определенных опций, когда несколько вызовов разделяют ключ. Они должны совпадать:

  • Функция handler
  • Функция transform
  • Массив pick
  • Функция getCachedData
  • Значение default
  • Опция deep

Эти могут безопасно отличаться:

  • server
  • lazy
  • immediate
  • dedupe
  • watch

Нарушение согласованности вызывает предупреждения при разработке и непредсказуемое поведение.

Безопасные стратегии для ключей

Для данных, специфичных для маршрута, включите параметры маршрута в ваш ключ:

const route = useRoute()
const { data } = await useAsyncData(
  `product-${route.params.id}`,
  () => $fetch(`/api/products/${route.params.id}`)
)

Для независимых экземпляров, которые не должны разделять состояние, используйте уникальные ключи:

const { data: sidebar } = await useAsyncData('users-sidebar', fetchUsers)
const { data: main } = await useAsyncData('users-main', fetchUsers)

Кэширование данных и поведение дедупликации в Nuxt

Nuxt автоматически дедуплицирует одновременные запросы с совпадающими ключами. Если три компонента запрашивают один и тот же ключ одновременно, выполняется только один сетевой запрос.

Опция dedupe управляет поведением обновления:

const { data, refresh } = await useFetch('/api/data', {
  dedupe: 'cancel' // отменяет ожидающие запросы перед запуском нового
})

В Nuxt 4.2 и выше поддержка отмены значительно улучшена. Когда это поддерживается, устаревшие ответы с предыдущих маршрутов могут быть отменены или проигнорированы при быстрой навигации, снижая риск кратковременного появления устаревших данных.

Подробнее: https://nuxt.com/docs/api/composables/use-fetch

Распространенные ошибки

Путаница между useFetch в Nuxt и других библиотеках

useFetch в Nuxt — это не то же самое, что useFetch из @vueuse/core или аналогичных утилит. Версия Nuxt автоматически обрабатывает гидратацию SSR payload. Использование useFetch из другой библиотеки полностью обходит это, вызывая двойную загрузку и несоответствия при гидратации.

Использование $fetch в setup без useAsyncData

Прямой вызов $fetch в <script setup> выполняется и на сервере, и на клиенте:

// ❌ Загружается дважды
const data = await $fetch('/api/users')

// ✅ Загружается один раз, гидратируется корректно
const { data } = await useFetch('/api/users')

Оставьте $fetch для обработчиков событий и клиентских взаимодействий.

Повторное использование ключей с конфликтующими опциями

Это вызывает предупреждения и ошибки:

// ❌ Конфликтующие опции deep
await useAsyncData('users', fetchUsers, { deep: false })
await useAsyncData('users', fetchUsers, { deep: true })

Ожидание данных до гидратации с server: false

Когда вы устанавливаете server: false, данные остаются null до завершения гидратации — даже если вы используете await для композабла.

Заключение

Модель загрузки данных в Nuxt 4 основана на серверном выполнении, гидратации payload и кэшировании на основе ключей. Держите ключи стабильными и уникальными для каждого источника данных. Обеспечьте согласованность опций при совместном использовании ключей в компонентах. Используйте useFetch или useAsyncData для SSR-безопасной загрузки и оставьте $fetch для клиентских взаимодействий.

Освойте эти паттерны, и вы избежите ошибок двойной загрузки и разделения состояния, которые расстраивают большинство разработчиков на Nuxt.

Часто задаваемые вопросы

Обычно это происходит, когда вы используете $fetch напрямую в script setup вместо useFetch или useAsyncData. Прямые вызовы $fetch выполняются и на сервере, и на клиенте. Оберните ваши загрузки в useFetch или useAsyncData, чтобы использовать гидратацию payload, которая загружает данные один раз на сервере и повторно использует их на клиенте.

Используйте useFetch при загрузке напрямую из URL — он автоматически обрабатывает ключи на основе URL. Используйте useAsyncData, когда вам нужна пользовательская логика, объединение нескольких вызовов API или явный контроль над ключом кэша. Оба обеспечивают одинаковые преимущества SSR-гидратации.

Компоненты с одинаковым ключом разделяют идентичное реактивное состояние. Чтобы сохранить данные независимыми, используйте уникальные ключи для каждого компонента. Например, используйте users-sidebar и users-main вместо просто users, когда два компонента загружают данные с одного endpoint, но нуждаются в отдельном состоянии.

Опция dedupe управляет тем, как Nuxt обрабатывает множественные вызовы refresh. Установка dedupe в cancel прерывает любой ожидающий запрос перед запуском нового. Это помогает избежать состояния гонки при быстрых взаимодействиях пользователя и гарантирует, что более новые ответы имеют приоритет, когда отмена поддерживается.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay