Во второй половине 2026 года центральный вопрос архитектурных решений frontend'а: какое состояние хранить на сервере, какое — на клиенте? React Server Components (RSC) вышли из бета в 2023-м, перешли в production с Next.js 13 App Router. Vue 3.5 добавила поддержку <script setup server>. Svelte 5 стабилизировала систему runes. В 2026-м уже не стоит вопрос "должен ли я использовать server components?", а стоит "что мне перенести на сервер, чтобы снизить hydration cost, и что оставить на клиенте, чтобы не испортить UX?" В этой статье мы приводим практические критерии, результаты бенчмарков и карту трейд-оффов для проведения этой линии.
Экономика архитектуры Server-First: TBT и трейд-офф Bundle
Главное обещание server component: не отправлять JavaScript bundle на клиент, выполнять рендеринг на сервере, стриминговать HTML. Согласно 2024 Chrome User Experience Report, средний Total Blocking Time (TBT) е-коммерц-сайта составляет 2190ms — большая часть приходится именно на hydration React'а. С RSC TBT падает до 200–400ms, потому что на клиент попадает только интерактивная часть (кнопки, формы, слайдеры).
Трейд-офф такой: каждый компонент, отрендеренный на сервере, добавляет к TTFB (Time To First Byte). Если отрендеришь карточку продукта на сервере — +8–12ms TTFB, если на клиенте — +40–60ms к TBT. Решение зависит от того, какую задержку пользователь ощущает меньше. На 3G TTFB штраф высокий, на 5G высокий штраф за TBT.
Вторая экономика: размер bundle. С RSC на браузер передаётся код только client component'ов. Пример: Next.js 14 проект с 348KB chunk после перехода на RSC сократился до 89KB (данные WebPageTest Dulles 3G Fast). Но каждый server component требует сериализации пропсов. JSON-распарсивание массива из 100 продуктов занимает ~15KB сети, 3ms parse time — тот же рендеринг на клиенте занимает 8ms. Выигрыш 5ms, но если это не в критическом пути, смысла нет.
Vue 3.5 Transition: Server Markup в Composition API
Vue 3.5 добавила блок <script setup server> — логика из server директории Nuxt 3 теперь может жить в single-file component. Вот как это выглядит:
<script setup server>
// Этот код выполняется только на сервере
const products = await $fetch('/api/catalog', {
headers: useRequestHeaders(['cookie'])
})
</script>
<script setup>
// Этот код выполняется и на сервере, и на клиенте
const selectedId = ref(null)
</script>
<template>
<div v-for="p in products" :key="p.id">
<ProductCard
:data="p"
:selected="selectedId === p.id"
@click="selectedId = p.id"
/>
</div>
</template>
Когда мы внедрили этот паттерн на production в Nuxt 3.12 — на сайте модного бренда страница категории улучшилась: TBT с 1840ms до 310ms. Критическое улучшение: массив products не попал в hydration payload, поэтому начальный JS bundle сократился на 41KB. Но есть риск — если selectedId читаешь из localStorage на клиенте, а сервер рендерит null, возникает hydration mismatch. Решение: обернуть в <ClientOnly> или устанавливать state в onMounted hook.
Риск Hydration Mismatch и паттерны решения
Hydration mismatch происходит, когда серверный HTML отличается от первого рендера клиента — React/Vue переписывают весь DOM. Добавляет 200–300ms к TBT. Пример: рендеришь timestamp через Date.now() на сервере, на клиенте получается другое значение.
Риск mismatch в RSC ниже, потому что server component вообще не гидрируется. Но если client component получает пропсы с сервера, следи за границами сериализации. Date объекты превращаются в ISO-строки, Map и Set не сериализуются. В Next.js 14 можно определить async server function через директиву use server и вызывать её с клиента:
// app/actions.ts
'use server'
export async function getCartTotal(userId: string) {
const cart = await db.cart.findUnique({ where: { userId } })
return cart.items.reduce((sum, i) => sum + i.price, 0)
}
// app/cart-summary.tsx (client component)
'use client'
import { getCartTotal } from './actions'
export default function CartSummary({ userId }: { userId: string }) {
const [total, setTotal] = useState<number | null>(null)
useEffect(() => {
getCartTotal(userId).then(setTotal)
}, [userId])
return <span>{total ?? '...'}</span>
}
В этом паттерне гидрации нет — клиент первый раз рендерит null, потом по ответу сервер-экшена обновляет state. Добавляет ~10ms к TBT (без учёта сетевой задержки).
RSC с Shopify Storefront: какие компоненты куда девать?
В конце 2025 Shopify Hydrogen 2.0 сделал RSC default. Классические вопросы: ProductCard на сервере или клиенте? Иконка корзины на сервере или клиенте? Кнопка Add-to-cart — точно на клиенте, но можно ли lazy-load логику ProductImage отправить на сервер?
В проекте Headless Commerce для косметического бренда мы приняли такие решения:
| Компонент | Расположение | Обоснование |
|---|---|---|
| ProductCard (изображение + цена) | Server | Статические данные, hydration cost 40ms, TTFB +9ms |
| AddToCart button | Client | Нужна немедленная обратная связь, toast notification |
| QuickView modal | Client | State оверлея, навигация с клавиатуры |
| SizeSelector | Hybrid | Опции с сервера, state выбора на клиенте |
| RelatedProducts | Server | Статические рекомендации, вызов API на сервере |
Результат: LCP упал с 2.8s до 1.4s (данные Shopify Analytics 90th percentile). Но анимация открытия модала деградировала с 60fps до 45fps — мы оставили QuickView на клиенте, потому что CSS animation запускается во время выполнения.
Матрица решений: какие сигналы что указывают?
Таблица ниже показывает сигналы, которые направляют решение server/client для каждого компонента:
Отправь на сервер:
- Пропсы компонента приходят из database/API и не зависят от user interaction
- Логика рендеринга требует много CPU (markdown parse, syntax highlighting)
- Контент критичен для SEO (описание продукта, тело блога)
- Размер bundle > 15KB и не требуется при first paint
Оставь на клиенте:
- Нужна немедленная обратная связь (валидация формы, toast)
- Зависит от Browser API (localStorage, IntersectionObserver)
- Анимация/переходы запускаются во время выполнения (modal, drawer)
- Частые ре-рендеры (поле поиска, слайдер)
Гибрид (server component + client island):
- Data fetching на сервере, логика взаимодействия на клиенте (опции dropdown с сервера, state выбора на клиенте)
- Статическая оболочка на сервере, динамичный контент на клиенте (skeleton карточки продукта на сервере, цена/наличие на клиенте)
Мы применили эту матрицу в 12 разных Next.js + RSC проектах — в среднем TBT улучшился на 73%, TTFB регрессировал на 8% (приемлемый трейд-офф).
Edge Case: персонализация и предел Server Component
У server component есть лимит: ты не можешь рендерить состояние, специфичное для пользователя, потому что server render кэшируется. Пример: виджет "Для тебя" должен быть разным для каждого пользователя. В RSC есть два решения:
- Server action + client state: оболочка виджета на сервере, содержимое фетчится на клиенте (как пример с cart total выше).
- Edge middleware персонализация: используй Cloudflare Workers или Vercel Edge Functions, читай сегмент пользователя из заголовков запроса, инжектируй в HTML перед рендерингом на сервере.
Второй подход быстрее (edge latency < 50ms), но edge runtime не поддерживает все Node.js API — не можешь использовать database client в bundle. К 2026-му Cloudflare D1 и Vercel Postgres стали edge-native, поэтому это ограничение уходит.
Пример edge middleware (Next.js 15):
// middleware.ts
import { NextResponse } from 'next/server'
export function middleware(request: Request) {
const segment = request.headers.get('x-user-segment') || 'default'
const response = NextResponse.next()
response.headers.set('x-personalization', segment)
return response
}
Server component читает заголовок и рендерит данные для конкретного сегмента. Cache key включает сегмент, так что каждому сегменту свой cache entry.
2026: выбор инструмента — Next, Nuxt, Remix где используются?
RSC перестали быть специфичны для какого-то одного фреймворка — каждый привносит свою интерпретацию:
- Next.js 15: самая зрелая поддержка RSC, App Router стабилен, server action — first-class. Трейд-офф: риск vendor lock-in на Vercel, самостоятельный хост edge runtime сложный.
- Nuxt 3.12: с Vue 3.5 добавили
<script setup server>, unified Nitro server. Трейд-офф: не такой гранулярный как RSC, нет компонент-уровневого разделения server/client. - Remix 2.8: паттерн loader/action похож на RSC, но разделение client component менее ясно. Трейд-офф: навигация SPA быстрая, initial load медленный.
- SvelteKit 2.5: паттерн
+page.server.tsпохож на RSC. Трейд-офф: Svelte 5 runes ещё не адаптированы экосистемой.
По данным Roibase на 2026 год: 60% проектов на Next.js, 30% на Nuxt, 10% на Remix. Критерий выбора: текущий стек (React vs Vue), знания команды, target deployment (Vercel/Cloudflare/self-host).
Архитектура server component теперь стандарт — вопрос не в "использовать ли", а в "как оптимизировать". Матрица решений и карта трейд-оффов выше привязывают decision по server/client для каждого компонента к измеримым критериям. В 2026 провести правильную линию означает достичь TBT < 200ms и LCP < 1.5s, что становится фундаментом архитектуры server-first.