Все уроки
ФронтендСредний14 мин чтенияПолезно знать

Vue 3 и Nuxt

Реактивные компоненты на Composition API, сторы Pinia и full-stack приложения на Nuxt с SSR.

  • Vue 3
  • Composition API
  • Pinia
  • Nuxt
  • SSR
Шаг 1/5
CSR: сервер присылает почти пустую HTML-заготовку и большой бандл JavaScript. Браузер крутит спиннер и берётся за работу.

За 30 секунд

Vue — JavaScript-фреймворк для построения пользовательских интерфейсов из компонентов, известный пологой кривой обучения и точной системой реактивности. Composition API во Vue 3 организует логику в переиспользуемые функции, а Pinia — его официальный стор состояния. Nuxt — full-stack фреймворк поверх Vue, играющий ту же роль, что Next.js для React: роутинг по файлам, серверный рендеринг, загрузка данных и серверные API-роуты в одном проекте.

Не в моём ежедневном стеке — я понимаю принципы и изучу детали, когда это понадобится проекту.

Почему это важно

  • Vue — один из самых популярных фронтенд-фреймворков, особенно сильный в Европе и Азии, и на нём построено много существующих продуктов и агентств.
  • Его точечная реактивность отслеживает, какие именно данные использует компонент, поэтому обновления эффективны без ручной мемоизации.
  • Single-file компоненты держат шаблон, логику и стили вместе, и многим командам их легко читать и вводить в них новых людей.
  • Nuxt даёт Vue-командам SSR, SEO-дружелюбные страницы, серверные роуты и гибридный рендеринг с сильными конвенциями — сравнимо с тем, что Next.js даёт React-командам.

Как это работает

01Идея: реактивные данные управляют представлением

Во Vue вы объявляете состояние, а шаблон описывает, как должен выглядеть UI для этого состояния. Когда состояние меняется, Vue обновляет только те части DOM, которые от него зависят. Под капотом Vue 3 оборачивает ваши данные в JavaScript Proxy; когда компонент рендерится, Vue записывает, какие реактивные свойства он прочитал, и когда одно из них меняется, перезапускает рендер только этого компонента. Представьте электронную таблицу: ячейки с формулами автоматически пересчитываются, когда меняются ячейки, на которые они ссылаются. Vue-приложение — это дерево компонентов, обычно написанных как Single-File Components (.vue-файлы) с шаблоном, скриптом и необязательными scoped-стилями. Vue 3.5 — текущая стабильная ветка; Vue 3.6 с новым «Vapor mode» на основе компилятора по состоянию на конец 2026 года находится на стадии release candidate.

02Компонент на Composition API

Внутри script setup ref() создаёт реактивное значение (в скрипте читается и пишется через .value, в шаблоне используется напрямую), reactive() делает реактивный объект, computed() вычисляет кэшируемые производные значения, а watch() выполняет побочные эффекты, когда меняются источники. Props объявляются через defineProps и начиная с Vue 3.5 их можно деструктурировать с сохранением реактивности. События поднимаются вверх через defineEmits, а defineModel создаёт двустороннюю привязку для v-model. В шаблонах используются директивы: v-if, v-for (с :key), v-bind (сокращение :) и v-on (сокращение @).

vue
<script setup lang="ts">
import { ref, computed } from "vue";
 
const { items = [] } = defineProps<{ items?: { id: number; name: string; price: number }[] }>();
const emit = defineEmits<{ select: [id: number] }>();
 
const query = ref("");
const filtered = computed(() =>
  items.filter((i) => i.name.toLowerCase().includes(query.value.toLowerCase()))
);
</script>
 
<template>
  <input v-model="query" placeholder="Search products" />
  <p v-if="!filtered.length">No results</p>
  <ul v-else>
    <li v-for="item in filtered" :key="item.id" @click="emit('select', item.id)">
      {{ item.name }} — {{ item.price.toFixed(2) }}
    </li>
  </ul>
</template>

03Composables: переиспользуемая логика

Composable — это обычная функция с именем useSomething, которая использует API реактивности Vue и возвращает реактивное состояние. Это аналог кастомного хука React во Vue, но выполняется он один раз при setup компонента, а не на каждом рендере, поэтому нет массивов зависимостей и ловушек с устаревшими замыканиями. Composables заменяют миксины из Vue 2, у которых были конфликты имён и неясное происхождение. Используйте их для загрузки данных, логики форм, браузерных API (онлайн-статус, media queries) и общего поведения. Библиотека VueUse предлагает сотни готовых composables. Хуки жизненного цикла вроде onMounted и onUnmounted можно вызывать внутри composables, чтобы навешивать и снимать слушатели.

ts
// composables/useOnline.ts
import { ref, onMounted, onUnmounted } from "vue";
 
export function useOnline() {
  const online = ref(typeof navigator === "undefined" ? true : navigator.onLine);
  const update = () => (online.value = navigator.onLine);
  onMounted(() => {
    window.addEventListener("online", update);
    window.addEventListener("offline", update);
  });
  onUnmounted(() => {
    window.removeEventListener("online", update);
    window.removeEventListener("offline", update);
  });
  return { online };
}

04Управление состоянием с Pinia

Для состояния, общего для многих компонентов, — например, вошедшего пользователя или корзины — официальный стор Vue это Pinia (он заменил Vuex). Стор объявляется через defineStore и может использовать тот же setup-синтаксис, что и компоненты: ref — это состояние, computed — геттеры, функции — экшены. Pinia типобезопасен, модулен (один стор на домен), поддерживает Vue DevTools и работает с SSR в Nuxt. Используйте storeToRefs при деструктуризации состояния, чтобы значения оставались реактивными. Не всё должно жить в сторе: серверные данные часто лучше держать в слое загрузки данных вроде useFetch в Nuxt или TanStack Query для Vue, а локальное состояние UI остаётся в компонентах.

ts
// stores/cart.ts
import { defineStore } from "pinia";
import { ref, computed } from "vue";
 
export const useCartStore = defineStore("cart", () => {
  const items = ref<{ id: number; qty: number; price: number }[]>([]);
  const total = computed(() => items.value.reduce((s, i) => s + i.qty * i.price, 0));
 
  function add(id: number, price: number) {
    const line = items.value.find((i) => i.id === id);
    line ? line.qty++ : items.value.push({ id, qty: 1, price });
  }
  return { items, total, add };
});

05Nuxt: full-stack фреймворк для Vue

Nuxt 4 — текущая мажорная версия (поддержка Nuxt 3 закончилась в середине 2026 года). Он даёт роутинг по файлам из app/pages, layouts, автоимпорт компонентов и composables, серверный рендеринг по умолчанию и директорию server на базе Nitro для API-роутов и middleware. Nitro через пресеты может деплоить одно и то же приложение на Node.js-серверы, serverless-платформы или edge-рантаймы. Nuxt 4 по умолчанию перенёс код приложения в директорию app/, отделив его от server/, и улучшил поведение загрузки данных и разделение TypeScript-проектов. Модули (@nuxt/image, @nuxt/content, @pinia/nuxt, @nuxtjs/i18n) добавляют возможности одной строкой конфига.

bash
npx nuxi@latest init my-shop
cd my-shop && npm run dev
 
# Project layout (Nuxt 4)
# app/pages/index.vue           -> /
# app/pages/products/[id].vue   -> /products/:id
# app/components/ProductCard.vue (auto-imported)
# app/composables/useCart.ts     (auto-imported)
# server/api/products/[id].get.ts -> GET /api/products/:id
# nuxt.config.ts

06Загрузка данных и серверные роуты в Nuxt

В страницах и компонентах используйте useFetch или useAsyncData. Они выполняются на сервере во время SSR, сериализуют результат в payload HTML и переиспользуют его на клиенте при гидратации, так что данные не загружаются дважды. Они возвращают реактивные data, status, error и функцию refresh, а принимают key, опции вроде lazy и server и реактивные параметры, при изменении которых данные загружаются заново. Для обработчиков событий вроде отправки формы используйте $fetch напрямую. Серверные роуты живут в server/api и экспортируют defineEventHandler; они выполняются только на сервере, поэтому могут читать секреты из runtimeConfig и работать с базами данных.

ts
// server/api/products/[id].get.ts
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, "id");
  const product = await findProduct(id); // your DB call
  if (!product) throw createError({ statusCode: 404, statusMessage: "Not found" });
  return product;
});
 
<!-- app/pages/products/[id].vue -->
<script setup lang="ts">
const route = useRoute();
const { data: product, status, error } = await useFetch(`/api/products/${route.params.id}`);
useSeoMeta({ title: () => product.value?.name ?? "Product" });
</script>

07Режимы рендеринга и деплой

Nuxt поддерживает универсальный рендеринг (SSR плюс гидратация), SPA-режим только на клиенте, статическую генерацию и гибридный рендеринг, где у каждого роута своё правило. Route rules в nuxt.config позволяют пререндерить маркетинговые страницы при сборке, кэшировать страницы товаров со stale-while-revalidate, рендерить дашборд только на клиенте и добавлять редиректы или заголовки — всё в одном приложении. Собирайте через nuxi build для серверной или serverless-цели или nuxi generate для статического сайта. Результат в .output самодостаточен: на VPS вы запускаете node .output/server/index.mjs за nginx или менеджером процессов; на Vercel, Netlify или Cloudflare пресет определяется автоматически.

ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@pinia/nuxt", "@nuxt/image"],
  runtimeConfig: {
    apiSecret: "",                       // NUXT_API_SECRET, server only
    public: { apiBase: "/api" },         // NUXT_PUBLIC_API_BASE, exposed to client
  },
  routeRules: {
    "/": { prerender: true },
    "/products/**": { swr: 600 },
    "/dashboard/**": { ssr: false },
    "/old-shop/**": { redirect: "/products/**" },
  },
});

Когда применять

  • Команды, которым нравятся HTML-подобные шаблоны и пологая кривая обучения или у которых уже есть опыт с Vue.
  • Контентные сайты, e-commerce и маркетинговые страницы, которым для SEO нужен SSR или пререндеринг, — на Nuxt.
  • Постепенное улучшение серверно-рендеримых страниц (Laravel, Django) интерактивными виджетами, куда Vue легко встраивается.
  • Админ-панели и дашборды, где экосистема Vue (Vuetify, PrimeVue, Element Plus, Nuxt UI) ускоряет поставку.

Когда не стоит

  • Когда команда и библиотеки компонентов уже на React; смешение экосистем удваивает поддержку.
  • Когда общий код с мобильным приложением на React Native — жёсткое требование; нативные варианты Vue гораздо менее зрелые.
  • Совсем маленькие статические страницы, где достаточно обычного HTML или лёгкого инструмента вроде Astro.

Альтернативы

  • React + Next.js

    Крупнейшая экосистема и рынок найма; Server Components и модель на JSX.

  • Svelte + SvelteKit

    Compiler-first, очень маленькие бандлы, реактивность на рунах.

  • Angular

    Enterprise-фреймворк «всё включено» с сигналами, DI и строгой структурой.

  • Astro

    Контентные сайты, отправляющие мало JavaScript; может встраивать Vue-компоненты как острова.

Лучшие практики

  1. Для всего нового кода используйте script setup с TypeScript и Composition API.
  2. Всегда давайте элементам v-for стабильный уникальный :key и никогда не сочетайте v-if и v-for на одном элементе.
  3. Выносите переиспользуемую логику в composables, а компоненты держите сфокусированными на рендеринге.
  4. В Nuxt загружайте данные страницы через useFetch или useAsyncData, а не $fetch внутри setup, чтобы избежать двойной загрузки.
  5. Храните секреты в серверном runtimeConfig и открывайте только публичные ключи в runtimeConfig.public.
  6. Используйте shallowRef для больших неизменяемых данных и computed для производных значений вместо watcher-ов, копирующих состояние.
  7. Линтите через ESLint плюс eslint-plugin-vue и проверяйте типы шаблонов через vue-tsc в CI.

Частые ошибки

  • Деструктуризация объекта reactive() или стора Pinia без toRefs или storeToRefs, которая молча ломает реактивность.
  • Использование watch для синхронизации двух частей состояния вместо computed-значения.
  • Обращение к window или localStorage во время setup при SSR, что вызывает серверные ошибки или hydration mismatch.
  • Вызов $fetch на верхнем уровне страницы Nuxt, из-за чего данные загружаются один раз на сервере и ещё раз на клиенте.
  • Хранение всего состояния приложения в Pinia, включая серверный кэш и временное состояние UI.

Глубже

08Внутри системы реактивности

Каждый реактивный объект — это Proxy, чья ловушка get вызывает track(target, key), а ловушка set — trigger(target, key). Эффекты, например функция рендера компонента, computed или watcher, выполняются в контексте active effect; каждое отслеживаемое чтение во время выполнения регистрирует эффект как подписчика этого ключа. При trigger Vue планирует подписанные эффекты; обновления компонентов группируются и сбрасываются асинхронно в микрозадаче — поэтому DOM обновлён только после nextTick(). Vue 3.4 сделал computed ленивыми и уведомляющими только тогда, когда их результат действительно меняется, а Vue 3.5 переписал внутренности на дизайн со счётчиком версий и двусвязным списком, что заметно сократило потребление памяти. Используйте markRaw для объектов, которые никогда не должны быть реактивными, например экземпляров библиотек карт или графиков.

ts
import { ref, computed, nextTick, markRaw, shallowRef } from "vue";
 
const count = ref(0);
const isEven = computed(() => count.value % 2 === 0); // only notifies on true<->false
 
count.value++;          // DOM not updated yet
await nextTick();       // now it is
 
// Large third-party instance: keep it out of reactivity
const map = shallowRef(markRaw(createMap("#map")));

09Оптимизации компилятора и Vapor mode

Компилятор шаблонов Vue — причина того, что virtual DOM во Vue дешевле наивного. Он поднимает (hoist) статические узлы, чтобы они создавались один раз, помечает динамические узлы patch-флагами (меняется только текст, только class) и группирует динамических потомков в блоки, так что diff полностью пропускает статическую структуру. Это возможно потому, что шаблоны статически анализируемы, в отличие от произвольного JSX. Vapor mode, который появится во Vue 3.6, идёт дальше: компонент, включённый через script setup vapor, компилируется в прямые DOM-операции, привязанные к реактивным эффектам, вообще без virtual DOM — как в Solid или Svelte. Он может взаимодействовать с обычными компонентами, так что после стабилизации команды смогут внедрять его покомпонентно на критичных к производительности экранах.

10Внутренности SSR в Nuxt: payload, ключи и кэширование

Во время SSR Nuxt выполняет ваши компоненты на сервере, и каждый результат useAsyncData или useFetch сохраняется под своим ключом в payload, сериализованном в HTML. При гидратации клиент читает данные из payload, а не загружает их заново. Ключи важны: два вызова с одним ключом делят данные, а автоматически сгенерированный из файла и строки ключ работает, только когда вызов находится на верхнем уровне setup. Загрузке внутри условия или цикла нужны явные ключи. routeRules в Nitro с swr или isr кэшируют отрендеренный HTML на уровне сервера или CDN, а defineCachedEventHandler кэширует ответы API. Помните, что серверное состояние общее для всех запросов: никогда не храните данные конкретного пользователя в переменных уровня модуля в серверном коде или composables.

ts
// Explicit key + transform to keep the payload small
const { data } = await useAsyncData(
  `product-${id}`,
  () => $fetch(`/api/products/${id}`),
  { pick: ["id", "name", "price"] }
);
 
// server/api/rates.get.ts — cached for 10 minutes
export default defineCachedEventHandler(async () => {
  return await $fetch("https://api.example.com/rates");
}, { maxAge: 600 });

11Производительность и масштабирование большой кодовой базы на Vue

Для размера бандла используйте defineAsyncComponent и ленивую загрузку на уровне роутов, чтобы каждая страница загружала только то, что ей нужно; Nuxt делит код по роутам автоматически, а префикс Lazy (LazyChart) откладывает компонент до момента рендера. Nuxt также поддерживает стратегии ленивой гидратации вроде hydrate-on-visible, чтобы интерактивные компоненты ниже первого экрана не тратили время main thread при загрузке. Для длинных списков используйте виртуальный скролл (vue-virtual-scroller или TanStack Virtual) и v-memo для дорогих строк. В больших кодовых базах Nuxt layers позволяют нескольким приложениям делить компоненты, конфиг и composables из базового слоя, а строгий TypeScript с vue-tsc ловит ошибки типов в шаблонах. Профилируйте через performance timeline во Vue DevTools перед оптимизацией.

Реальные ситуации

01

Проблемы с SEO у магазина на Vue SPA

Проблема

Интернет-магазин, построенный как Vue SPA только на клиенте, плохо ранжируется. Страницы товаров показывают краулерам пустую оболочку, а в превью соцсетей нет заголовков и изображений.

Как решить

Мигрируйте на Nuxt, переиспользовав существующие компоненты и сторы Pinia. Перенесите роуты в app/pages, замените клиентскую загрузку на useFetch, чтобы данные товаров рендерились на сервере, и добавьте useSeoMeta для заголовков, описаний и Open Graph изображений каждого товара. Используйте routeRules, чтобы пререндерить страницы категорий и применить swr-кэширование к страницам товаров, чтобы сервер не нагружался на каждом просмотре. Добавьте модуль sitemap и канонические URL. Проверьте через view-source и rich results test, что контент есть в исходном HTML.

02

Дашборд зависает при фильтрации 10,000 строк

Проблема

Админская таблица на Vue тормозит на секунды, когда пользователь печатает в поле фильтра. Профилирование показывает тяжёлую работу реактивности и рендеринга на каждое нажатие клавиши.

Как решить

Храните сырой датасет в shallowRef, чтобы Vue не проксировал глубоко 10,000 объектов. Сделайте debounce поискового ввода через refDebounced из VueUse и вычисляйте отфильтрованные результаты через computed, чтобы они пересчитывались только при изменении входных данных. Рендерите через виртуальный скроллер, чтобы в DOM существовали только видимые строки. Если строки — тяжёлые компоненты, используйте v-memo с ключом по данным строки. Для данных серверного масштаба перенесите фильтрацию и пагинацию в API. Перепрофилируйте в DevTools, чтобы убедиться, что main thread остаётся отзывчивым.

03

Данные пользователей утекают между посетителями после SSR

Проблема

После включения SSR пользователь на мгновение увидел в шапке имя другого пользователя. Приложение хранит текущего пользователя в composable с ref на уровне модуля.

Как решить

Состояние уровня модуля на сервере общее для всех запросов, поэтому пользователь одного запроса утекает в рендер другого. Перенесите пользователя в стор Pinia или useState в Nuxt — оба создают состояние на каждый запрос на сервере и гидратируют его на клиенте. Загружайте его в серверном плагине или route middleware из сессионной cookie и никогда не кэшируйте персонализированные страницы через swr или правила CDN. Проверьте остальные composables на ref уровня модуля и добавьте тест, который рендерит два параллельных запроса с разными пользователями.

Шпаргалка

Новое Vue-приложение (Vite)
npm create vue@latest
Новое Nuxt-приложение
npx nuxi@latest init my-app
Prop с двусторонней привязкой
const model = defineModel<string>()
Template ref (3.5+)
const input = useTemplateRef<HTMLInputElement>("input")
Сохранить реактивность значений стора
const { items, total } = storeToRefs(useCartStore())
SSR-данные в Nuxt
const { data, status, refresh } = await useFetch("/api/items")
Виджет только на клиенте
<ClientOnly><MapView /></ClientOnly>
Кэширование по роутам
routeRules: { "/blog/**": { swr: 3600 } }
Проверка типов шаблонов
npx vue-tsc --noEmit
Сборка Nuxt под Node
npx nuxi build && node .output/server/index.mjs

Вопросы и ответы

Q1Чем реактивность Vue отличается от модели React?

React перезапускает функцию компонента, когда меняются его состояние или props, и сравнивает полученный virtual DOM; лишнюю работу вы контролируете мемоизацией и массивами зависимостей. Vue отслеживает зависимости автоматически: реактивные объекты — это Proxy, и во время рендера Vue записывает, какие свойства были прочитаны. Когда одно из них меняется, перерендериваются только компоненты, которые его использовали. Код setup выполняется один раз, а не на каждом рендере, поэтому устаревших замыканий нет. В результате Vue-приложениям редко нужна ручная мемоизация. Модель React более явная и функциональная; модель Vue — более автоматическая и близкая к сигналам, которые с тех пор переняли и другие фреймворки.

Q2ref или reactive: что использовать?

Команда Vue рекомендует ref как основной API. ref работает с любым типом значения, включая примитивы, и вы можете заменить значение целиком или передавать его дальше без потери реактивности. reactive работает только с объектами и теряет реактивность, если его деструктурировать или переприсвоить переменную, — это частый источник багов. В шаблонах ref разворачиваются автоматически, так что цена .value проявляется только в скрипте. Я иногда использую reactive для локального объекта формы, который никогда не переприсваивается, ref — для всего остального, плюс shallowRef для больших данных, которые заменяются, а не мутируются.

Q3Что Nuxt добавляет поверх Vue?

Vue — библиотека компонентов; Nuxt — фреймворк приложения. Nuxt добавляет роутинг по файлам и layouts, серверный рендеринг с гидратацией, пререндеринг и правила кэширования по роутам, composables загрузки данных, которые передают серверные данные на клиент, сервер Nitro для API-роутов и middleware, SEO-хелперы, автоимпорты, runtime-конфигурацию и экосистему модулей для изображений, контента, i18n и auth. Он также через пресеты деплоит один и тот же проект на Node.js, serverless или edge-платформы. Это аналог Next.js для React: все эти части можно собрать самому из Vite и Vue Router, но Nuxt делает их согласованными.

Q4Что такое hydration mismatch и как его избежать?

При SSR сервер рендерит HTML, а клиент затем гидратирует его, привязывая реактивность Vue к существующему DOM. Если первый рендер на клиенте даёт разметку, отличную от серверной, Vue предупреждает о несовпадении и может перерендерить части страницы, вызывая мерцание или баги. Типичные причины — чтение window, localStorage или текущего времени во время setup, случайные ID и форматирование, зависящее от локали. Решения: переносите браузерный код в onMounted, оборачивайте браузерные виджеты в ClientOnly, используйте useId для стабильных ID, а время или локаль передавайте с сервера через useState или payload.

Q5Pinia или composables для общего состояния?

Composable, который объявляет ref внутри функции, создаёт новое состояние для каждого компонента; объявление ref на уровне модуля делает состояние общим, но на сервере это состояние во время SSR утекает между запросами разных пользователей, а это проблема безопасности. Pinia это решает: сторы создаются на каждый экземпляр приложения, поэтому SSR-запросы изолированы, а вы получаете поддержку DevTools, плагины и гидратацию состояния стора. Моё правило: локальная переиспользуемая логика — в composables, глобальное клиентское состояние — в сторах Pinia, серверные данные — в useFetch или библиотеке запросов. В Nuxt useState — лёгкий SSR-безопасный вариант для небольших общих значений.

Q6Как бы вы мигрировали большое приложение с Vue 2 на Vue 3?

Поддержка Vue 2 закончилась в конце 2023 года, так что миграция — это вопрос безопасности. Сначала я бы обновился до Vue 2.7, где бэкпортированы Composition API и script setup, и начал писать новый код так. Затем заменил бы библиотеки, не поддерживающие Vue 3: Vuex на Pinia, старый роутер на Vue Router 4, UI-киты на их версии для Vue 3. Migration build (@vue/compat) может запускать приложение на Vue 3 с поведением Vue 2 и предупреждениями, так что устаревшее исправляется постепенно. Переход с Webpack на Vite обычно происходит в то же время. Делают это безопасным хорошие end-to-end тесты.

Вопросы уровня senior

S1Как бы вы структурировали большое Nuxt-приложение, общее для нескольких команд?

Я бы использовал Nuxt layers: базовый слой с дизайн-системой, общими composables, auth и API-клиентом и слои фич или продуктов, которые его расширяют. Каждая команда владеет своим слоем в монорепозитории под управлением pnpm workspaces и Turborepo или Nx, со строгими границами, закреплёнными lint-правилами, чтобы фичи не импортировали внутренности друг друга. Доступ к данным идёт через типизированные API-клиенты, сгенерированные из OpenAPI, а не через разрозненные вызовы $fetch. Общее состояние минимально и живёт в сторах Pinia с понятными владельцами. CI запускает vue-tsc, юнит-тесты на Vitest и end-to-end тесты на Playwright для каждого затронутого слоя. Так сохраняется одно деплоимое приложение с ясным владением, что обычно лучше, чем сразу прыгать в микрофронтенды.

S2Сравните Nuxt и Next.js для нового продукта. Что решит выбор?

Технически они близки: оба предлагают файловый роутинг, SSR, статический и гибридный рендеринг, серверные роуты и деплой на edge. Решение в основном про людей и экосистему. У Next.js больше рынок найма, React Server Components для точечного серверного рендеринга и React Native для общего кода с мобильными приложениями. Nuxt предлагает более простую реактивность Vue, меньше бойлерплейта благодаря автоимпортам и конвенциям, переносимость Nitro между хостингами без вендорских фич и сильную экосистему модулей. Если команда знает React или нужен общий код с мобайлом, я выбираю Next.js. Если команда на Vue или продукт контентный и SEO-нагруженный, а небольшая команда ценит конвенции, Nuxt — отличный выбор. Переписывание ради смены фреймворка редко окупается.

S3Как безопасно организовать аутентификацию в Nuxt с SSR?

Я храню сессию в HttpOnly, Secure, SameSite=Lax cookie, которую выставляет сервер, и никогда — токен в localStorage, чтобы она была доступна во время SSR и не читалась скриптами. Серверные роуты и server middleware проверяют сессию на каждом запросе; клиентский route middleware только улучшает UX ранним редиректом, поскольку его можно обойти. Во время SSR пробрасывайте входящую cookie при вызове собственного API через useRequestFetch или useRequestHeaders. Состояние пользователя кладётся в useState или Pinia, чтобы оно было на уровне запроса. Персонализированные страницы исключаются из swr и CDN-кэширования. Для входа через сторонние сервисы OAuth-потоки обрабатывает nuxt-auth-utils или SDK провайдера, а для изменяющих запросов с аутентификацией по cookie обязательна CSRF-защита.

Глоссарий

Single-File Component
.vue-файл, объединяющий шаблон, скрипт и стили компонента в одном месте.
Composition API
Функциональный способ писать логику компонентов во Vue 3 через ref, computed, watch и хуки жизненного цикла.
ref
Реактивный контейнер для любого значения; в скрипте читается и пишется через .value.
Composable
Переиспользуемая функция, использующая API реактивности Vue; по конвенции называется useSomething.
Pinia
Официальная библиотека управления состоянием во Vue с типобезопасными модульными сторами и поддержкой SSR.
Гидратация
Привязка клиентом интерактивности к HTML, который сервер уже отрендерил.
Nitro
Серверный движок Nuxt, который выполняет API-роуты и деплоится на Node, serverless или edge-платформы.
Route rules
Конфиг Nuxt, сопоставляющий URL-шаблоны с поведением рендеринга, кэширования, редиректов и заголовков.
Vapor mode
Готовящийся режим компиляции Vue, который обновляет DOM напрямую без virtual DOM.

Есть идея продукта?Давайте создадим его.

Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.