Skip to main content

Що таке 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 (ручний роутинг):

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.jsNuxt.js (v3)
РендерингТільки на клієнтіSSR, SSG, SPA або гібрид на вибір
РоутингРучний конфіг vue-routerАвтогенерація з папки pages/
Час старту15-30 хв boilerplatenpx nuxi init → одразу готово
SEOРучні <meta> або плагіниВбудований useHead() + серверний HTML
Отримання данихonMounted + fetchuseFetch / 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.

vue
<script setup> // Неправильно: виконується тільки на клієнті onMounted(async () => { data.value = await $fetch('/api/products') }) </script>

Сервер відправляє порожню сторінку. Пошуковики не бачать контенту. Користувач бачить миготіння перед появою даних.

vue
<script setup> // Правильно: виконується на сервері, дані вже в HTML const { data } = await useAsyncData('products', () => $fetch('/api/products')) </script>

2. Пряме використання window або document в компонентах

vue
<script setup> // Крашиться на сервері - в Node.js немає window const width = window.innerWidth </script>

Nuxt запускає компонент спочатку на Node.js. Там window не існує.

vue
<script setup> // Варіант 1: composable з VueUse (сам обробляє SSR) const { width } = useWindowSize() // Варіант 2: перевірка через process.client if (process.client) { const width = window.innerWidth } </script>

3. API-ключі в <script setup>

vue
<script setup> const key = 'sk-secret-123' // Потрапить в клієнтський бандл </script>

Nuxt пакує і серверний, і клієнтський JS. Ключ дістанеться до браузера. Використовуй useRuntimeConfig() - приватні ключі залишаються тільки на сервері, публічні йдуть в runtimeConfig.public у nuxt.config.ts.

4. Перевірка process.client всередині глобального middleware

ts
// 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. Жодного конфігу роутів не потрібно.

vue
<!-- 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 завжди клієнтський.

ts
// 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 сторінки. Швидше і безпечніше.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?