useAsyncData: гибкая загрузка
useAsyncData принимает любую async-функцию, а не только URL: для нескольких источников, своих SDK и преобразования ответа — это правильный выбор.
Суть: useAsyncData(key, fn) выполняет произвольную асинхронную функцию при SSR с кешированием по ключу. В отличие от useFetch, не привязан к одному URL: подходит для комбинации источников, кастомных клиентов и трансформации данных перед попаданием в компонент.
useFetch прекрасен для простого случая «один GET, один URL». Но реальность сложнее: иногда нужно дёрнуть два эндпоинта и слить результат, иногда — использовать SDK (например, клиент CMS), иногда — преобразовать ответ перед показом. Для этого есть useAsyncData — более общий инструмент.
Он принимает ключ и асинхронную функцию. Функция может делать что угодно — главное, вернуть данные:
<script setup>
const { data } = await useAsyncData("dashboard", async () => {
const [user, stats] = await Promise.all([
$fetch("/api/user"),
$fetch("/api/stats"),
])
return { user, stats } // объединённый результат
})
</script>
Здесь мы делаем два запроса параллельно и возвращаем объединённый объект — useFetch так не умеет, потому что он про один URL. По сути useFetch(url) — это сокращение для useAsyncData(url, () => $fetch(url)). Знание этого снимает путаницу: useFetch — частный случай useAsyncData.
Полезная опция — transform: преобразовать ответ до того, как он попадёт в data и payload. Это уменьшает размер payload и упрощает шаблон.
Когда что брать один GET, один URL -> useFetch несколько источников/слияние -> useAsyncData свой SDK / не-HTTP операция -> useAsyncData нужно трансформировать ответ -> transform в любом из них
Стоит держать в голове и обратную сторону SSR-загрузки: данные, попавшие в payload, увеличивают размер HTML-страницы. Если вы тянете огромный массив, а показываете из него лишь пару полей, весь массив поедет к клиенту в составе страницы. Поэтому полезно забирать с сервера ровно то, что нужно для рендера, а тяжёлую фильтрацию и обрезку делать ещё на стороне API или через опцию transform. Это держит payload компактным, а первый экран — лёгким, что напрямую влияет на скорость загрузки на слабых устройствах.
Как работает под капотом
useAsyncData оборачивает вашу функцию в SSR-совместимый жизненный цикл: выполняет на сервере, кладёт результат в payload по ключу, на клиенте находит его и не выполняет функцию повторно. Ключ обязателен (в useFetch он выводится из URL автоматически) — именно по нему связываются серверный и клиентский результаты и работает дедупликация. В Nuxt 4 данные по умолчанию хранятся как shallowRef для производительности на вложенных объектах.
Смоделируем слияние нескольких источников и кеш по ключу — то, чем useAsyncData отличается от useFetch:
// useAsyncData: любая async-функция + кеш по ключу.
const cache = {};
async function api(name, value) {
return { [name]: value }; // имитация источника
}
async function useAsyncData(key, fn) {
if (cache[key]) return { data: cache[key], cached: true };
const data = await fn(); // выполняем произвольную логику
cache[key] = data; // кешируем по ключу
return { data, cached: false };
}
(async () => {
const first = await useAsyncData("dashboard", async () => {
const [user, stats] = await Promise.all([
api("user", "Алиса"),
api("stats", 42),
]);
return { ...user, ...stats }; // СЛИЯНИЕ источников
});
console.log("Первый вызов:", first);
const second = await useAsyncData("dashboard", async () => ({}));
console.log("Повтор по ключу:", second); // из кеша, функция не звалась
})();
Попробуй сам ▶ — функция слила два источника в один объект, а повторный вызов по тому же ключу вернул кешированное без новой работы.
Частые ошибки
- Забыть уникальный ключ. Без него Nuxt не свяжет сервер и клиент и может задублировать запрос.
- Делать в setup то, что не нужно при SSR. Тяжёлую клиентскую логику выносите за useAsyncData.
- Возвращать несериализуемое. Результат идёт в payload — функции и классы туда не положишь.
Best practices
- Несколько источников или своя логика —
useAsyncData; один URL —useFetch. - Используйте
transform, чтобы не тащить в payload лишние поля ответа. - Задавайте осмысленные уникальные ключи — это и кеш, и дедупликация.
Итог: useAsyncData — гибкая основа загрузки данных, а useFetch — её удобный частный случай для одного URL. Дальше разберём третий инструмент — императивный $fetch для событий.