NuxtSeniorExperience

Представьте, приложение на Nuxt стало медленно открываться или часто перерисовываться. Как вы будете искать причину?

Начните с Lighthouse и nuxi analyze для измерения. Высокий TTFB — смотрите серверные запросы и routeRules с cache/isr. Частые перерисовки — Vue DevTools Profiler и аудит реактивных зависимостей.

Диагностика производительности Nuxt-приложения

Медленное открытие и частые перерисовки — разные симптомы с разными причинами. Диагностику нужно проводить последовательно, начиная с измерений.

Этап 1: Измерить, не угадывать

# Lighthouse в headless-режиме для стабильных цифр
npx lighthouse https://your-app.com --output=json --chrome-flags="--headless" | \
  jq '{fcp: .audits["first-contentful-paint"].displayValue, lcp: .audits["largest-contentful-paint"].displayValue, tbt: .audits["total-blocking-time"].displayValue}'

Зафиксируйте метрики до изменений: FCP, LCP, TBT, TTI, TTFB. Без baseline невозможно оценить прогресс.

Этап 2: Медленное открытие — диагностика TTFB

Высокий TTFB (>600 ms) указывает на проблему на сервере:

  • Медленные запросы к БД или внешним API в server routes — добавьте логирование времени.
  • Отсутствие кеширования: используйте routeRules с cache или isr.
  • Cold start serverless-функции — рассмотрите keep-alive или переход на Node.js-сервер.
// nuxt.config.ts — включить кеш для статичных маршрутов
export default defineNuxtConfig({
  routeRules: {
    '/blog/**': { isr: 3600 },   // ISR — кеш на 1 час
    '/api/catalog': { cache: { maxAge: 60 } },
  },
})

Этап 3: Медленное открытие — размер бандла

# Анализ бандла
npx nuxi analyze

Ищите: дублирование зависимостей, тяжёлые библиотеки в initial chunk (lodash, moment, тяжёлые UI-библиотеки). Решения: dynamic import, замена на lighter-аналоги (date-fns вместо moment), tree-shaking настройка.

Этап 4: Частые перерисовки — Vue DevTools Profiler

Откройте Vue DevTools (отдельное расширение браузера) → вкладка Performance → запустите запись → воспроизведите проблему. Ищите компоненты с высокой частотой рендеринга.

// Типичная причина: реактивный объект пересоздаётся на каждый рендер
// Плохо:
const options = computed(() => ({ page: currentPage.value, limit: 20 }))
watch(options, fetchData) // options — новый объект при каждом computed, watch триггерится всегда

// Хорошо:
watch(currentPage, fetchData) // отслеживать только нужный примитив

Этап 5: Профилирование SSR

// server/api/debug-timing.get.ts
export default defineEventHandler(async (event) => {
  const start = Date.now()
  const data = await expensiveQuery()
  console.log(`Query time: ${Date.now() - start}ms`)
  return data
})

Этап 6: Проверить useFetch кеширование

// Если useFetch повторяет запросы при каждой навигации — проверьте ключ
const { data } = await useFetch('/api/products', {
  key: `products-${categoryId}`, // уникальный ключ = нет лишних запросов
  getCachedData: (key, nuxtApp) => nuxtApp.payload.data[key] // использовать payload
})

Подводные камни

  • Lighthouse в режиме dev-сервера даёт нерелевантные цифры — всегда измеряйте production-сборку.
  • Частые перерисовки в списках: компонент без :key или с нестабильным ключом заставляет Vue уничтожать и пересоздавать DOM-элементы вместо патчинга.
  • useAsyncData без lazy: true блокирует навигацию до завершения запроса — пользователь видит белый экран вместо skeleton.
  • Тяжёлые watcher-цепочки: watch → изменение state → computed → watch — цикл может триггериться тысячи раз в секунду без очевидной причины.
  • Nuxt Image без width/height вызывает layout shift (CLS) — всегда указывайте размеры.
  • Импорт иконочных библиотек целиком (например, import * as Icons from '@heroicons/vue') раздувает бандл — используйте named imports.
  • SSR-рендеринг тяжёлых markdown-файлов через Nuxt Content без кеша нагружает CPU при каждом запросе.

What hurts your answer

  • Сразу обвинять Nuxt, не проверив соседние слои системы
  • Чинить симптом без минимального воспроизведения и evidence
  • Не учитывать версии, конфигурацию, окружение и recent changes

What they're listening for

  • Умеет локализовать проблему вокруг Nuxt
  • Двигается от симптома к гипотезам и проверкам
  • Отличает баг инструмента от ошибки использования или окружения

Related topics