**Nuxt** — это мета-фреймворк над Vue. Если Vue даёт строительные блоки, то Nuxt — готовую архитектуру:
| Vue | Nuxt |
|-----|------|
| Нужно настраивать Vue Router | Файловый роутинг из коробки |
| Нужно настраивать SSR вручную | SSR/SSG/CSR — единая конфигурация |
| Импорты вручную | Авто-импорт компонентов и composables |
| Нет серверного кода | server/ директория для API |
| Нужно настраивать TypeScript | TypeScript из коробки |
my-nuxt-app/
├── pages/ # Файловый роутинг
│ ├── index.vue # → /
│ ├── about.vue # → /about
│ └── users/
│ ├── index.vue # → /users
│ └── [id].vue # → /users/42 (динамический)
├── components/ # Авто-импортируемые компоненты
│ └── MyButton.vue # Используется без импорта
├── composables/ # Авто-импортируемые composables
│ └── useCounter.ts # Доступен во всех компонентах
├── server/ # Серверный код (Node.js/Nitro)
│ └── api/
│ └── users.get.ts # GET /api/users
├── layouts/ # Layouts страниц
│ └── default.vue
├── middleware/ # Навигационные guard'ы
├── plugins/ # Vue-плагины
├── public/ # Статические файлы
├── app.vue # Корневой компонент
└── nuxt.config.ts # КонфигурацияNuxt автоматически импортирует:
// В pages/index.vue — НЕТ НУЖДЫ писать:
import { ref, computed } from 'vue' // ← не нужно
import { useRoute } from 'vue-router' // ← не нужно
import { useMyComposable } from '@/composables/useMyComposable' // ← не нужно
// Всё доступно напрямую:
const count = ref(0)
const route = useRoute()
const { data } = useMyComposable()Nuxt использует **Nitro** — серверный движок. Файлы в server/api/ создают API-маршруты:
// server/api/users.get.ts
export default defineEventHandler(async (event) => {
const users = await db.query('SELECT * FROM users')
return users // автоматически сериализуется в JSON
})
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
return await db.findUser(id)
})Разделение конфигурации на сервер и клиент:
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// Только серверная сторона (секретные ключи)
databaseUrl: process.env.DATABASE_URL,
apiSecret: process.env.API_SECRET,
// Публичная — доступна на клиенте тоже
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api',
appName: 'My App',
}
}
})// В компоненте
const config = useRuntimeConfig()
console.log(config.public.apiBase) // '/api' — доступно везде
// config.databaseUrl — только на сервере!// nuxt.config.ts
export default defineNuxtConfig({
ssr: true, // SSR по умолчанию
// ssr: false, // CSR (SPA режим)
// Hybrid rendering (разные режимы для разных маршрутов):
routeRules: {
'/': { prerender: true }, // SSG — статически при сборке
'/blog/**': { swr: 3600 }, // ISR — кэш на 1 час
'/admin/**': { ssr: false }, // CSR — без SSR
'/api/**': { cors: true }, // CORS для API
}
})Симуляция авто-импорта Nuxt и файловой системы маршрутов — как Nuxt читает структуру папок
// Симулируем, как Nuxt генерирует маршруты из файловой структуры
// и как работает авто-импорт.
// --- Файловый роутинг ---
function generateRoutes(fileTree) {
const routes = []
function processDir(entries, prefix = '') {
for (const [name, value] of Object.entries(entries)) {
if (typeof value === 'object') {
// Директория
const segmentName = name.startsWith('[') && name.endsWith(']')
? ':' + name.slice(1, -1) // [id] → :id (динамический параметр)
: name
processDir(value, prefix + '/' + segmentName)
} else {
// Файл .vue
const file = name.replace('.vue', '')
let routePath
if (file === 'index') {
routePath = prefix || '/' // index.vue → родительский маршрут
} else {
const seg = file.startsWith('[') && file.endsWith(']')
? ':' + file.slice(1, -1)
: file
routePath = prefix + '/' + seg
}
routes.push({
path: routePath,
file: (prefix || '') + '/' + name,
isDynamic: routePath.includes(':'),
})
}
}
}
processDir(fileTree)
return routes.sort((a, b) => {
// Статические маршруты раньше динамических
if (a.isDynamic === b.isDynamic) return a.path.localeCompare(b.path)
return a.isDynamic ? 1 : -1
})
}
// --- Авто-импорт ---
class AutoImporter {
constructor() {
this._imports = new Map()
this._modules = new Map()
}
// Регистрируем модуль (аналог файла в composables/)
registerModule(name, exports) {
this._modules.set(name, exports)
// Авто-импорт всех экспортов
for (const [key, value] of Object.entries(exports)) {
this._imports.set(key, { from: name, value })
}
console.log(`[AutoImport] Зарегистрирован модуль "${name}": ${Object.keys(exports).join(', ')}`)
}
// Получить авто-импортируемое значение
resolve(name) {
const imp = this._imports.get(name)
if (!imp) throw new Error(`[AutoImport] "${name}" не найден. Проверьте composables/ или components/`)
return imp.value
}
// Список всех авто-импортов
list() {
return [...this._imports.entries()].map(([name, { from }]) => ({ name, from }))
}
}
// --- useRuntimeConfig симуляция ---
function createRuntimeConfig(config) {
const isServer = typeof window === 'undefined'
return {
...config.public, // публичное — всегда
...(isServer ? config : {}), // приватное — только сервер
public: config.public,
}
}
// === Демо: файловый роутинг ===
console.log('=== Файловый роутинг ===')
const fileStructure = {
'index.vue': 'component', // → /
'about.vue': 'component', // → /about
'blog': {
'index.vue': 'component', // → /blog
'[slug].vue': 'component', // → /blog/:slug
},
'users': {
'index.vue': 'component', // → /users
'[id]': {
'index.vue': 'component', // → /users/:id
'edit.vue': 'component', // → /users/:id/edit
}
},
'admin': {
'index.vue': 'component', // → /admin
'settings.vue': 'component', // → /admin/settings
}
}
const routes = generateRoutes(fileStructure)
routes.forEach(r => {
console.log(` ${r.isDynamic ? '[dynamic]' : '[static] '} ${r.path.padEnd(25)} ← ${r.file}`)
})
// === Демо: авто-импорт ===
console.log('\n=== Авто-импорт ===')
const autoImport = new AutoImporter()
// Регистрируем как Nuxt регистрирует composables/
autoImport.registerModule('composables/useCounter', {
useCounter: () => ({ count: 0, increment: () => {} })
})
autoImport.registerModule('composables/useFetch', {
useFetch: async (url) => ({ data: null, pending: true }),
useAsyncData: async (key, fn) => ({ data: await fn(), pending: false }),
})
// Используем без явного import
const useCounter = autoImport.resolve('useCounter')
const counter = useCounter()
console.log('useCounter():', counter)
console.log('\nВсе авто-импорты:')
autoImport.list().forEach(({ name, from }) =>
console.log(` ${name} (из ${from})`)
)
// === Демо: runtimeConfig ===
console.log('\n=== runtimeConfig ===')
const runtimeConfig = createRuntimeConfig({
databaseUrl: 'postgres://...', // только сервер
apiSecret: 'super-secret', // только сервер
public: {
apiBase: '/api',
appName: 'My Nuxt App',
}
})
console.log('public.apiBase:', runtimeConfig.apiBase)
console.log('public.appName:', runtimeConfig.appName)
**Nuxt** — это мета-фреймворк над Vue. Если Vue даёт строительные блоки, то Nuxt — готовую архитектуру:
| Vue | Nuxt |
|-----|------|
| Нужно настраивать Vue Router | Файловый роутинг из коробки |
| Нужно настраивать SSR вручную | SSR/SSG/CSR — единая конфигурация |
| Импорты вручную | Авто-импорт компонентов и composables |
| Нет серверного кода | server/ директория для API |
| Нужно настраивать TypeScript | TypeScript из коробки |
my-nuxt-app/
├── pages/ # Файловый роутинг
│ ├── index.vue # → /
│ ├── about.vue # → /about
│ └── users/
│ ├── index.vue # → /users
│ └── [id].vue # → /users/42 (динамический)
├── components/ # Авто-импортируемые компоненты
│ └── MyButton.vue # Используется без импорта
├── composables/ # Авто-импортируемые composables
│ └── useCounter.ts # Доступен во всех компонентах
├── server/ # Серверный код (Node.js/Nitro)
│ └── api/
│ └── users.get.ts # GET /api/users
├── layouts/ # Layouts страниц
│ └── default.vue
├── middleware/ # Навигационные guard'ы
├── plugins/ # Vue-плагины
├── public/ # Статические файлы
├── app.vue # Корневой компонент
└── nuxt.config.ts # КонфигурацияNuxt автоматически импортирует:
// В pages/index.vue — НЕТ НУЖДЫ писать:
import { ref, computed } from 'vue' // ← не нужно
import { useRoute } from 'vue-router' // ← не нужно
import { useMyComposable } from '@/composables/useMyComposable' // ← не нужно
// Всё доступно напрямую:
const count = ref(0)
const route = useRoute()
const { data } = useMyComposable()Nuxt использует **Nitro** — серверный движок. Файлы в server/api/ создают API-маршруты:
// server/api/users.get.ts
export default defineEventHandler(async (event) => {
const users = await db.query('SELECT * FROM users')
return users // автоматически сериализуется в JSON
})
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
return await db.findUser(id)
})Разделение конфигурации на сервер и клиент:
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// Только серверная сторона (секретные ключи)
databaseUrl: process.env.DATABASE_URL,
apiSecret: process.env.API_SECRET,
// Публичная — доступна на клиенте тоже
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api',
appName: 'My App',
}
}
})// В компоненте
const config = useRuntimeConfig()
console.log(config.public.apiBase) // '/api' — доступно везде
// config.databaseUrl — только на сервере!// nuxt.config.ts
export default defineNuxtConfig({
ssr: true, // SSR по умолчанию
// ssr: false, // CSR (SPA режим)
// Hybrid rendering (разные режимы для разных маршрутов):
routeRules: {
'/': { prerender: true }, // SSG — статически при сборке
'/blog/**': { swr: 3600 }, // ISR — кэш на 1 час
'/admin/**': { ssr: false }, // CSR — без SSR
'/api/**': { cors: true }, // CORS для API
}
})Симуляция авто-импорта Nuxt и файловой системы маршрутов — как Nuxt читает структуру папок
// Симулируем, как Nuxt генерирует маршруты из файловой структуры
// и как работает авто-импорт.
// --- Файловый роутинг ---
function generateRoutes(fileTree) {
const routes = []
function processDir(entries, prefix = '') {
for (const [name, value] of Object.entries(entries)) {
if (typeof value === 'object') {
// Директория
const segmentName = name.startsWith('[') && name.endsWith(']')
? ':' + name.slice(1, -1) // [id] → :id (динамический параметр)
: name
processDir(value, prefix + '/' + segmentName)
} else {
// Файл .vue
const file = name.replace('.vue', '')
let routePath
if (file === 'index') {
routePath = prefix || '/' // index.vue → родительский маршрут
} else {
const seg = file.startsWith('[') && file.endsWith(']')
? ':' + file.slice(1, -1)
: file
routePath = prefix + '/' + seg
}
routes.push({
path: routePath,
file: (prefix || '') + '/' + name,
isDynamic: routePath.includes(':'),
})
}
}
}
processDir(fileTree)
return routes.sort((a, b) => {
// Статические маршруты раньше динамических
if (a.isDynamic === b.isDynamic) return a.path.localeCompare(b.path)
return a.isDynamic ? 1 : -1
})
}
// --- Авто-импорт ---
class AutoImporter {
constructor() {
this._imports = new Map()
this._modules = new Map()
}
// Регистрируем модуль (аналог файла в composables/)
registerModule(name, exports) {
this._modules.set(name, exports)
// Авто-импорт всех экспортов
for (const [key, value] of Object.entries(exports)) {
this._imports.set(key, { from: name, value })
}
console.log(`[AutoImport] Зарегистрирован модуль "${name}": ${Object.keys(exports).join(', ')}`)
}
// Получить авто-импортируемое значение
resolve(name) {
const imp = this._imports.get(name)
if (!imp) throw new Error(`[AutoImport] "${name}" не найден. Проверьте composables/ или components/`)
return imp.value
}
// Список всех авто-импортов
list() {
return [...this._imports.entries()].map(([name, { from }]) => ({ name, from }))
}
}
// --- useRuntimeConfig симуляция ---
function createRuntimeConfig(config) {
const isServer = typeof window === 'undefined'
return {
...config.public, // публичное — всегда
...(isServer ? config : {}), // приватное — только сервер
public: config.public,
}
}
// === Демо: файловый роутинг ===
console.log('=== Файловый роутинг ===')
const fileStructure = {
'index.vue': 'component', // → /
'about.vue': 'component', // → /about
'blog': {
'index.vue': 'component', // → /blog
'[slug].vue': 'component', // → /blog/:slug
},
'users': {
'index.vue': 'component', // → /users
'[id]': {
'index.vue': 'component', // → /users/:id
'edit.vue': 'component', // → /users/:id/edit
}
},
'admin': {
'index.vue': 'component', // → /admin
'settings.vue': 'component', // → /admin/settings
}
}
const routes = generateRoutes(fileStructure)
routes.forEach(r => {
console.log(` ${r.isDynamic ? '[dynamic]' : '[static] '} ${r.path.padEnd(25)} ← ${r.file}`)
})
// === Демо: авто-импорт ===
console.log('\n=== Авто-импорт ===')
const autoImport = new AutoImporter()
// Регистрируем как Nuxt регистрирует composables/
autoImport.registerModule('composables/useCounter', {
useCounter: () => ({ count: 0, increment: () => {} })
})
autoImport.registerModule('composables/useFetch', {
useFetch: async (url) => ({ data: null, pending: true }),
useAsyncData: async (key, fn) => ({ data: await fn(), pending: false }),
})
// Используем без явного import
const useCounter = autoImport.resolve('useCounter')
const counter = useCounter()
console.log('useCounter():', counter)
console.log('\nВсе авто-импорты:')
autoImport.list().forEach(({ name, from }) =>
console.log(` ${name} (из ${from})`)
)
// === Демо: runtimeConfig ===
console.log('\n=== runtimeConfig ===')
const runtimeConfig = createRuntimeConfig({
databaseUrl: 'postgres://...', // только сервер
apiSecret: 'super-secret', // только сервер
public: {
apiBase: '/api',
appName: 'My Nuxt App',
}
})
console.log('public.apiBase:', runtimeConfig.apiBase)
console.log('public.appName:', runtimeConfig.appName)
Реализуй функцию `parseNuxtRoute(filePath)`, которая принимает путь к Vue-файлу относительно папки pages/ и возвращает объект `{ path, params, isDynamic }`. Правила: index.vue в конце → убрать /index, [param].vue → /:param (динамический сегмент), [[param]].vue → /:param? (опциональный параметр). Затем реализуй `matchRoute(routes, url)` — находит подходящий маршрут из массива и возвращает `{ route, params }` или null.
В parseNuxtRoute: segments = filePath.replace(".vue","").split("/"). Маппинг сегмента: if (seg.startsWith("[[") && seg.endsWith("]]")) return ":" + seg.slice(2,-2) + "?". Убери "index" из конца: if (segments.at(-1) === "index") segments.pop(). path = "/" + segments.join("/"). В matchRoute: разбей оба пути по "/", сравни длины, затем перебери сегменты: если сегмент маршрута начинается с ":" — захвати параметр, иначе сравни точно.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке