Vue + Nuxt: SSR, server routes, auto-imports
Nuxt — фреймворк поверх Vue с SSR, файловой маршрутизацией, автоимпортами и server routes. Основы Nuxt для Vue-разработчика.
Что такое Nuxt
Nuxt — фреймворк поверх Vue, который добавляет:
- SSR (Server-Side Rendering) и SSG (Static Site Generation)
- Файловую маршрутизацию — роуты из структуры папок
- Автоимпорты — компоненты, composables, утилиты
- Server routes — API прямо во фронтенд-проекте
- SEO-оптимизацию — meta-теги, og:tags из коробки
Создание проекта:
npx nuxi@latest init my-app
cd my-app
npm run dev
Структура проекта
my-app/
├── app/
│ ├── components/ ← автоимпорт компонентов
│ ├── composables/ ← автоимпорт composables
│ ├── layouts/ ← шаблоны страниц
│ ├── pages/ ← файловая маршрутизация
│ └── app.vue ← корневой компонент
├── server/
│ ├── api/ ← API-эндпоинты
│ ├── routes/ ← серверные роуты
│ └── middleware/ ← серверная middleware
├── content/ ← Markdown-контент
├── public/ ← статические файлы
├── nuxt.config.ts ← конфигурация
└── package.json
Файловая маршрутизация
Структура папок app/pages/ определяет роуты:
pages/
├── index.vue → /
├── about.vue → /about
├── users/
│ ├── index.vue → /users
│ ├── [id].vue → /users/:id
│ └── [id]/
│ └── settings.vue → /users/:id/settings
└── blog/
├── index.vue → /blog
└── [...slug].vue → /blog/* (catch-all)
Доступ к параметрам:
<script setup lang="ts">
const route = useRoute()
const id = route.params.id
</script>
Навигация:
<template>
<NuxtLink to="/about">О проекте</NuxtLink>
<NuxtLink :to="`/users/${userId}`">Профиль</NuxtLink>
</template>
Автоимпорты
Nuxt автоматически импортирует:
Компоненты
Любой .vue-файл в app/components/ доступен без импорта:
components/
├── AppHeader.vue → <AppHeader>
├── AppFooter.vue → <AppFooter>
└── user/
└── UserCard.vue → <UserCard>
Composables
Файлы use*.ts в app/composables/:
// composables/useCounter.ts
export function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => count.value++
return { count, increment }
}
<script setup lang="ts">
// Без импорта!
const { count, increment } = useCounter()
</script>
Vue и Nuxt утилиты
ref, computed, watch, onMounted, useRouter, useRoute, useFetch, useState и многие другие — без импортов.
Data Fetching
useFetch
<script setup lang="ts">
const { data: users, pending, error, refresh } = await useFetch<User[]>('/api/users')
</script>
<template>
<div v-if="pending">Загрузка...</div>
<div v-else-if="error">{{ error.message }}</div>
<ul v-else>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
<button @click="refresh()">Обновить</button>
</template>
useAsyncData
Для произвольных асинхронных операций:
const { data } = await useAsyncData('users', () => $fetch<User[]>('/api/users'))
Параметры запроса
const route = useRoute()
const { data: user } = await useFetch<User>(`/api/users/${route.params.id}`)
// С query-параметрами
const { data: search } = await useFetch('/api/search', {
query: { q: searchQuery },
})
// С watch — автоматический перезапрос
const { data } = await useFetch('/api/users', {
query: { page },
watch: [page],
})
Server Routes
API-эндпоинты прямо в проекте — powered by Nitro:
GET
// server/api/users/index.ts
export default defineEventHandler(async () => {
const users = await db.users.findMany()
return users
})
С параметрами
// server/api/users/[id].ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const user = await db.users.findById(Number(id))
if (!user) {
throw createError({ statusCode: 404, message: 'Пользователь не найден' })
}
return user
})
POST
// server/api/users/index.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
if (!body.name || !body.email) {
throw createError({ statusCode: 400, message: 'name и email обязательны' })
}
const user = await db.users.create(body)
return user
})
С query-параметрами
// server/api/search.ts
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const page = Number(query.page) || 1
const limit = Number(query.limit) || 10
const results = await db.users.search({
q: query.q as string,
page,
limit,
})
return results
})
Layouts
Шаблоны страниц в app/layouts/:
<!-- layouts/default.vue -->
<template>
<div>
<AppHeader />
<main>
<slot />
</main>
<AppFooter />
</div>
</template>
<!-- pages/about.vue -->
<script setup lang="ts">
definePageMeta({
layout: 'default',
})
</script>
Layout по умолчанию — layouts/default.vue. Можно сменить через definePageMeta.
Meta-теги и SEO
<script setup lang="ts">
useHead({
title: 'Главная страница',
meta: [
{ name: 'description', content: 'Описание страницы' },
{ property: 'og:title', content: 'Главная' },
{ property: 'og:image', content: '/og-image.png' },
],
})
</script>
Динамические meta:
const { data: article } = await useFetch(`/api/articles/${slug}`)
useHead({
title: () => article.value?.title ?? 'Загрузка...',
meta: [
{ name: 'description', content: () => article.value?.excerpt ?? '' },
],
})
Nuxt Config
// nuxt.config.ts
export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
'@nuxt/ui',
'@nuxt/content',
'@nuxtjs/tailwindcss',
],
runtimeConfig: {
// Серверные (не暴露 клиенту)
apiSecret: process.env.API_SECRET,
// Публичные (доступны на клиенте)
public: {
apiBase: process.env.API_BASE ?? 'http://localhost:3000',
},
},
app: {
head: {
title: 'Моё приложение',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
],
},
},
})
Render Modes
Nuxt поддерживает несколько режимов рендеринга:
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // SSG — статический HTML
'/api/**': { cors: true }, // CORS для API
'/admin/**': { ssr: false }, // SPA — без SSR
'/blog/**': { swr: 3600 }, // ISR — кэш на 1 час
},
})
| Режим | Когда |
|---|---|
| SSR (default) | SEO важен, динамический контент |
SPA (ssr: false) | Дашборды, админки |
SSG (prerender: true) | Лендинги, документация |
SWR/ISR (swr: N) | Блоги, каталоги |
State
useState — SSR-безопасное состояние (cross-request state sharing):
const counter = useState('counter', () => 0)
Отличие от ref: useState разделяет состояние между компонентами на сервере и клиенте без проблем гидратации.
Middleware
Route middleware
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const auth = useAuthStore()
if (!auth.isLoggedIn && to.path.startsWith('/dashboard')) {
return navigateTo('/login')
}
})
<script setup lang="ts">
definePageMeta({
middleware: 'auth',
})
</script>
Глобальная middleware — файл с суффиксом .global:
// middleware/Analytics.global.ts
export default defineNuxtRouteMiddleware((to) => {
console.log('Переход:', to.path)
})
Итог
Nuxt — мощный фреймворк поверх Vue. Файловая маршрутизация упрощает структуру, автоимпорты убирают бойлерплейт, server routes дают API без отдельного бэкенда. SSR/SSG/ISR настраиваются через routeRules. Для Vue-проектов, где нужен SEO или полный стек, Nuxt — лучший выбор.