NuxtSeniorExperience
Какие production-риски есть у Nuxt: hydration, bundle size, caching, accessibility, browser compatibility или observability?
Ключевые production-риски Nuxt: hydration mismatch при SSR из-за различий server/client окружений, раздутый bundle без code splitting, неверная стратегия кеширования API, проблемы доступности динамического контента.
Production-риски Nuxt: глубокий разбор
1. Hydration Mismatch
Самая частая ошибка в Nuxt — несоответствие между HTML, сгенерированным на сервере, и результатом hydration на клиенте.
// ПРОБЛЕМА: Date.now() разный на сервере и клиенте
const timestamp = ref(Date.now())
// Vue выбросит: [Vue warn] Hydration node mismatch
// РЕШЕНИЕ 1: ClientOnly wrapper
<ClientOnly>
<TimestampWidget />
<template #fallback>
<div>Loading...</div>
</template>
</ClientOnly>
// РЕШЕНИЕ 2: useHydration / onMounted
const timestamp = ref<number | null>(null)
onMounted(() => { timestamp.value = Date.now() })
// РЕШЕНИЕ 3: nuxt plugin с mode: 'client'
// plugins/analytics.client.ts
export default defineNuxtPlugin(() => {
// выполняется только на клиенте
window.analytics?.init()
})
2. Bundle Size
# Анализ бандла
npx nuxi build --analyze
# или
NUXT_ANALYZE=true npx nuxi build
// nuxt.config.ts — оптимизация
export default defineNuxtConfig({
// Автоматический import только используемых компонентов
components: {
dirs: [{ path: '~/components', pathPrefix: false }],
},
// Lazy-load тяжёлых компонентов
// В шаблоне: <LazyHeavyChart /> — загружается только при необходимости
vite: {
build: {
rollupOptions: {
output: {
manualChunks: {
'chart-libs': ['chart.js', 'vue-chartjs'],
'editor': ['@tiptap/core', '@tiptap/vue-3'],
},
},
},
},
},
// Внешние зависимости не включаем в server bundle
nitro: {
externals: {
inline: ['some-large-lib'],
},
},
});
3. Кеширование и ISR
// server/api/products.get.ts
export default defineCachedEventHandler(async (event) => {
const products = await fetchFromDB()
return products
}, {
maxAge: 60, // кешировать 60 секунд
name: 'products-list',
getKey: (event) => {
const query = getQuery(event)
return `products-${query.category}-${query.page}`
},
// Инвалидация при изменении данных:
// await useStorage().removeItem('nitro:handlers:products-list')
})
// nuxt.config.ts — route rules
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/blog/**': { isr: 3600 },
'/admin/**': { ssr: false, headers: { 'Cache-Control': 'no-store' } },
'/api/**': { cors: true },
},
})
4. Accessibility
<!-- Проблема: SPA-навигация не анонсирует смену страницы screen reader'у -->
<!-- Решение: aria-live region для анонсов навигации -->
<!-- layouts/default.vue -->
<template>
<div>
<div
aria-live="polite"
aria-atomic="true"
class="sr-only"
>
{{ pageTitle }} — загружено
</div>
<NuxtPage />
</div>
</template>
// Обновляем заголовок при навигации
const route = useRoute()
const pageTitle = computed(() => route.meta.title as string || 'Страница')
useHead({ title: pageTitle })
5. Browser Compatibility
// nuxt.config.ts
export default defineNuxtConfig({
vite: {
build: {
target: 'es2015', // поддержка старых браузеров
},
},
// Polyfills через @vitejs/plugin-legacy
})
Подводные камни
- Hydration mismatch от
Math.random(),Date.now(), localStorage в шаблоне — любые значения, разные на сервере и клиенте, ломают hydration. - useAsyncData без явного
key: Nuxt генерирует ключ по имени файла и строке вызова — при минификации или переименовании файлов кеш стает невалидным. - Неверный
maxAgeв Cache-Control для авторизованных страниц: CDN кеширует персональный ответ и отдаёт другим пользователям. - Lazy-компоненты (
<LazyX />) без#fallbackслота показывают пустое место во время загрузки — CLS (Cumulative Layout Shift) в Core Web Vitals. - Server routes без валидации входных данных: Nuxt не добавляет автоматическую санитизацию — передайте
getValidatedQuery/readValidatedBodyс zod-схемами. useFetchна клиенте делает HTTP-запрос на сервер снова (не переиспользует SSR-данные) если ключи не совпадают — двойной запрос и мерцание UI.- Отсутствие
error.vueкастомной страницы ошибок: при 500 пользователь видит стандартный Nitro error page без брендинга. - Observable производительность: без явного логирования
useServerTiming()невозможно определить, на каком этапе SSR тратится время — добавьте server timing headers в production.
What hurts your answer
- Говорить только о запуске Nuxt, но не об эксплуатации
- Не упоминать observability, обновления, безопасность и rollback
- Описывать риски абстрактно, без способов их снижать
What they're listening for
- Видит production-риски Nuxt
- Говорит про monitoring, rollout, rollback и безопасность
- Умеет ранжировать риски по вероятности и влиянию