Що таке Nuxt.js і чим він відрізняється від Vue.js?
Nuxt.js - це мета-фреймворк побудований поверх Vue.js, який додає server-side rendering (SSR), роутинг на основі файлів та авто-імпорти. Структура папок стає повноцінним додатком без ручного написання налаштувань.
Теорія
TL;DR
- Vue.js - це сире тісто: ти сам пишеш роутер, налаштовуєш SSR, підключаєш залежності. Nuxt - це готова форма: той самий Vue всередині, але він сам читає папку
pages/і робить решту. - Головна різниця: Vue потребує ручного конфігу
vue-router; Nuxt генерує роути з назв файлів. - Nuxt рендерить на сервері за замовчуванням. Vue рендерить у браузері.
- Правило вибору: публічний сайт з SEO → Nuxt. Внутрішній дашборд без SEO → Vue.
- Nuxt 3 використовує Nitro, який запускається на Node.js, Vercel або Cloudflare Workers без змін в коді.
Швидкий приклад
Найпомітніша різниця - роутинг. У Vue пишеш конфіг-файл. У Nuxt просто створюєш файл у правильній папці.
Vue.js (ручний роутинг):
// main.js - все це пишеш сам
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import Home from './pages/Home.vue'
import About from './pages/About.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
const router = createRouter({ history: createWebHistory(), routes })
createApp({}).use(router).mount('#app')Nuxt.js (роутинг на основі файлів):
pages/
index.vue → /
about.vue → /about
products/
[id].vue → /products/:id (динамічний роут, без конфігу)Жодного конфіг-файлу. Nuxt сканує pages/ під час білду і генерує роутер сам. Відкриваєш /about - отримуєш about.vue, відрендерений на сервері.
Ключова різниця
Nuxt сканує pages/, components/ і composables/ через Vite, генерує конфіг Vue Router і серверний бандл Nitro. Коли приходить запит, Nitro запускає компонент сторінки на Node.js, отримує дані через useAsyncData або useFetch на сервері, і повертає готовий HTML. Vue гідратує його на клієнті без повторного запиту даних - Nuxt автоматично передає серверний payload разом з HTML. Google бачить реальний контент, а не порожній <div id="app">.
Коли що використовувати
- Маркетинговий або продуктовий сайт → Nuxt. Пошуковики мають читати контент, тому SSR важливий.
- Внутрішній дашборд або адмін-панель → Vue. Без SEO, без сервера, простіший деплой.
- Статичний блог або документація → Nuxt з
nuxt generate. Пре-рендер в HTML, роздача з CDN. - Headless e-commerce → Nuxt з серверними роутами. Багато API, але сторінки товарів потребують індексації.
- Швидкий прототип → Nuxt. Файловий підхід скорочує час налаштування до кількох хвилин.
Таблиця порівняння
| Функція | Vue.js | Nuxt.js (v3) |
|---|---|---|
| Рендеринг | Тільки на клієнті | SSR, SSG, SPA або гібрид на вибір |
| Роутинг | Ручний конфіг vue-router | Автогенерація з папки pages/ |
| Час старту | 15-30 хв boilerplate | npx nuxi init → одразу готово |
| SEO | Ручні <meta> або плагіни | Вбудований useHead() + серверний HTML |
| Отримання даних | onMounted + fetch | useFetch / useAsyncData (з сервера) |
| Розбиття бандлу | Ручне налаштування | Авто на кожну сторінку |
| Сервер | Немає (додаєш сам) | Nitro (Node.js, Vercel, Cloudflare edge) |
| Авто-імпорти | Ні | Так - composables, компоненти, утиліти |
| Коли брати | SPA, бібліотеки, вбудовані віджети | Публічні сайти, e-commerce, full-stack |
Як Nuxt обробляє запит
Під час білду Vite-плагін Nuxt читає папку pages/ і генерує конфіг Vue Router. Окремо компілюється серверний бандл Nitro. Коли приходить запит, Nitro знаходить відповідний компонент, запускає його на Node.js, виконує всі useAsyncData та useFetch виклики на сервері, серіалізує результат в HTML. Браузер отримує готову сторінку. Vue гідратує її без повторних запитів - дані вже передані в payload разом з HTML.
Типові помилки
1. Отримання даних в onMounted для SSR-сторінок
Цей патерн - найпоширеніша помилка в код-рев'ю у розробників, які переходять з Vue SPA на Nuxt.
<script setup>
// Неправильно: виконується тільки на клієнті
onMounted(async () => {
data.value = await $fetch('/api/products')
})
</script>Сервер відправляє порожню сторінку. Пошуковики не бачать контенту. Користувач бачить миготіння перед появою даних.
<script setup>
// Правильно: виконується на сервері, дані вже в HTML
const { data } = await useAsyncData('products', () => $fetch('/api/products'))
</script>2. Пряме використання window або document в компонентах
<script setup>
// Крашиться на сервері - в Node.js немає window
const width = window.innerWidth
</script>Nuxt запускає компонент спочатку на Node.js. Там window не існує.
<script setup>
// Варіант 1: composable з VueUse (сам обробляє SSR)
const { width } = useWindowSize()
// Варіант 2: перевірка через process.client
if (process.client) {
const width = window.innerWidth
}
</script>3. API-ключі в <script setup>
<script setup>
const key = 'sk-secret-123' // Потрапить в клієнтський бандл
</script>Nuxt пакує і серверний, і клієнтський JS. Ключ дістанеться до браузера. Використовуй useRuntimeConfig() - приватні ключі залишаються тільки на сервері, публічні йдуть в runtimeConfig.public у nuxt.config.ts.
4. Перевірка process.client всередині глобального middleware
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to) => {
// Неправильно: пропуск серверної перевірки розкриває HTML захищеної сторінки
if (process.client && !useCookie('token').value) {
return navigateTo('/login')
}
})Без серверної перевірки Nuxt рендерить HTML захищеної сторінки і лише потім редиректить. Правильний варіант - прибрати process.client повністю. Сервер перехоплює запит ще до генерації HTML.
Де використовується в продакшені
- Netflix (Voraz): Nuxt 2 для SSR-лендингів відео, пізніше мігрували на Nuxt 3 для edge-деплою через Nitro.
- Adobe Portfolio: Статична генерація сайтів дизайнерів, роздача з CDN.
- Decathlon: E-commerce каталог з
useAsyncDataдля серверного рендерингу сторінок товарів. - Nuxt Commerce: Open-source Shopify headless стартер з серверними API-роутами і SSR.
Питання на співбесіді
Q: В чому різниця між useFetch і useAsyncData у Nuxt?
A: useFetch - це скорочення, яке автоматично генерує ключ кешу з URL і викликає useAsyncData всередині. useAsyncData приймає ручний ключ і кастомну функцію-фетчер, що дає контроль над кешуванням і дедуплікацією. Для простих випадків - useFetch. Для кастомного фетчера або передбачуваного ключа - useAsyncData.
Q: Що таке SSR і SSG у Nuxt і коли що обирати?
A: SSR рендерить кожну сторінку на сервері під час запиту - підходить для динамічних даних: каталоги товарів, дашборди. SSG пре-рендерить сторінки під час білду і роздає статичний HTML з CDN - підходить для блогів, документації, маркетингових сторінок де контент змінюється рідко. Запускаєш nuxt generate і отримуєш статику.
Q: Чим Nitro у Nuxt 3 відрізняється від сервера Nuxt 2?
A: Nuxt 2 використовував кастомний сервер на базі Express. Nitro в Nuxt 3 - це окремий серверний рушій з екосистеми UnJS. Він компілюється в легкий бандл, який запускається на Node.js, Deno, Cloudflare Workers або Vercel Edge Functions без змін в коді додатку.
Q: Vue 3 SPA працює нормально, але SEO страждає. Як мігрувати на Nuxt 3?
A: Перемісти сторінки з src/views/ в pages/, видали ручний конфіг vue-router, замінив onMounted-фетчинг на useAsyncData, додай app.vue як кореневий лейаут, запусти npx nuxi upgrade. Після міграції стеж за hydration mismatch - де використовував браузерні API без перевірки, буде помилка на сервері. Обгорни такі місця в <ClientOnly> або перевірку process.client.
Приклади
Сторінка товару з SSR та динамічним роутингом
Файл pages/products/[id].vue автоматично обробляє будь-який URL вигляду /products/:id. Жодного конфігу роутів не потрібно.
<!-- pages/products/[id].vue -->
<script setup>
const route = useRoute()
// Виконується на сервері - дані вже в HTML до завантаження браузером
const { data: product, pending } = await useAsyncData(
`product-${route.params.id}`,
() => $fetch(`/api/products/${route.params.id}`)
)
// SEO-мета з серверних даних - Google бачить реальний заголовок
useHead({
title: product.value?.name ?? 'Товар',
meta: [{ name: 'description', content: product.value?.description }]
})
</script>
<template>
<div v-if="pending">Завантаження...</div>
<div v-else-if="product">
<h1>{{ product.name }}</h1>
<!-- Заголовок вже є в серверному HTML - Google його проіндексує -->
<img :src="product.image" :alt="product.name" width="300" />
<p>{{ product.description }}</p>
</div>
</template>Сервер відправляє сторінку з назвою товару вже в HTML. Google індексує її. Клієнт гідратує без другого мережевого запиту - Nuxt автоматично передає payload useAsyncData разом з HTML.
Auth middleware з серверним редиректом
Цей момент дивує розробників, які прийшли з Vue SPA, де middleware завжди клієнтський.
// middleware/auth.global.ts - запускається для кожного роуту, на сервері і клієнті
export default defineNuxtRouteMiddleware((to) => {
const token = useCookie('token')
// Жодного process.client тут - це навмисно
// На сервері: неавторизований користувач не отримує HTML захищеної сторінки
if (!token.value) {
return navigateTo('/login')
}
})У Vue SPA браузер рендерить захищену сторінку і лише потім редиректить - HTML вже був у пам'яті. З цим патерном Nuxt відправляє 302 редирект ще до генерації HTML сторінки. Швидше і безпечніше.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.