React Server Components: впровадження для продуктивності
Типова ситуація: сторінка каталогу в Next.js App Router завантажує 200 КБ JS на клієнт, хоча 80% коду — статичний вивід товарів. Ви використовуєте getServerSideProps, API routes і весь UI обгортаєте в 'use client'. Як результат — повільне завантаження на мобільних та низький LCP. Рішення — Next.js з React Server Components (RSC): компоненти, які виконуються тільки на сервері і не надсилають жодного байта JS у браузер. React documentation states: "Server Components render on the server and send zero JavaScript to the client." Це змінює підхід до архітектури: ви можете напряму звертатися до БД з компонента, не створюючи окремий API-шар. Ми допомагаємо впровадити RSC у ваш проєкт — від аудиту до повної міграції.
Чому RSC — не просто покращення SSR?
SSR рендерить React-компоненти на сервері та надсилає HTML, але все одно вимагає завантаження та гідратації JS на клієнті. RSC — окремий тип компонентів, які ніколи не гідратуються та не додають JS у бандл. Порівняння:
| Характеристика | SSR (getServerSideProps) | RSC (App Router) |
|---|---|---|
| HTML на сервері | Так | Так |
| Гідратація JS | Повна гідратація | Тільки клієнтські компоненти |
| Розмір JS-бандла | Весь компонент | Тільки інтерактивні частини |
| Запити до БД | Через API (окремий шар) | Прямі імпорти в компоненті |
| Стан на клієнті | useState / Redux | Там, де потрібно |
Типові помилки при розмежуванні: часто весь UI-компонент позначають 'use client' через один обробник. Правильніше винести інтерактивну частину в окремий клієнтський компонент, а решту залишити серверним. Не можна використовувати серверні компоненти в провайдерах контексту — вони мають бути клієнтськими.
Як розмежувати Server Components і Client Components?
Приклад: серверний компонент, який отримує товари з БД і рендерить список, а клієнтський — тільки кнопка додавання в кошик.
// СЕРВЕРНИЙ компонент (за замовчуванням в App Router)
// Цей код НІКОЛИ не потрапляє в браузер
import { db } from '@/lib/db'; // Прямий імпорт Prisma/Drizzle — нормально
import { unstable_cache } from 'next/cache';
const getProducts = unstable_cache(
async (categoryId: string) => {
return db.product.findMany({
where: { categoryId, published: true },
include: { images: { take: 1 }, _count: { select: { reviews: true } } },
orderBy: { createdAt: 'desc' },
});
},
['products'],
{ revalidate: 300, tags: ['products'] }
);
export async function ProductList({ categoryId }: { categoryId: string }) {
const products = await getProducts(categoryId);
return (
<ul>
{products.map(product => (
<li key={product.id}>
<ProductCard product={product} />
<AddToCartButton productId={product.id} />
</li>
))}
</ul>
);
}
// КЛІЄНТСЬКИЙ компонент — 'use client' обов'язковий
'use client';
import { useState, useTransition } from 'react';
import { addToCart } from '@/actions/cart'; // Server Action
export function AddToCartButton({ productId }: { productId: string }) {
const [isPending, startTransition] = useTransition();
return (
<button
onClick={() => startTransition(() => addToCart(productId))}
disabled={isPending}
>
{isPending ? 'Додаємо...' : 'В кошик'}
</button>
);
}
Що можуть і чого не можуть Server Components
Server Components можуть: async/await на верхньому рівні, прямі запити до БД без API, читання змінних оточення, імпорт server-only бібліотек, рендеринг інших серверних та клієнтських компонентів. Не можуть: використовувати useState, useEffect, useContext, обробляти браузерні події, використовувати браузерні API, приймати функції як пропси.
Server Actions — мутації без API
// app/actions/products.ts
'use server';
import { revalidateTag } from 'next/cache';
import { redirect } from 'next/navigation';
import { db } from '@/lib/db';
import { auth } from '@/lib/auth';
import { z } from 'zod';
const UpdateProductSchema = z.object({
name: z.string().min(1).max(255),
price: z.number().positive(),
description: z.string().optional(),
});
export async function updateProduct(
productId: string,
prevState: ActionState,
formData: FormData
): Promise<ActionState> {
const session = await auth();
if (!session?.user) return { error: 'Unauthorized' };
const parsed = UpdateProductSchema.safeParse({
name: formData.get('name'),
price: Number(formData.get('price')),
description: formData.get('description'),
});
if (!parsed.success) {
return { error: parsed.error.flatten().fieldErrors };
}
await db.product.update({
where: { id: productId },
data: parsed.data,
});
revalidateTag('products');
redirect(`/products/${productId}`);
}
// Використання у формі через useActionState
'use client';
import { useActionState } from 'react';
import { updateProduct } from '@/actions/products';
export function EditProductForm({ product }: { product: Product }) {
const [state, action, isPending] = useActionState(
updateProduct.bind(null, product.id),
null
);
return (
<form action={action}>
<input name="name" defaultValue={product.name} required />
{state?.error?.name && <p>{state.error.name[0]}</p>}
<input name="price" type="number" defaultValue={product.price} />
<button type="submit" disabled={isPending}>
{isPending ? 'Збереження...' : 'Зберегти'}
</button>
</form>
);
}
Оптимізація: context та провайдери
Поширена помилка — обгортати весь додаток у Client Component з провайдером:
// Погано: весь layout стає клієнтським
'use client';
export function Layout({ children }) {
return <ThemeProvider><AuthProvider>{children}</AuthProvider></ThemeProvider>;
}
// Добре: провайдери ізольовані, children — серверні
// providers.tsx
'use client';
export function Providers({ children }: { children: React.ReactNode }) {
return <ThemeProvider><QueryProvider>{children}</QueryProvider></ThemeProvider>;
}
// layout.tsx — серверний
import { Providers } from './providers';
export default async function RootLayout({ children }) {
const session = await auth(); // Серверний запит у layout
return (
<html>
<body>
<Providers session={session}>{children}</Providers>
</body>
</html>
);
}
Як впровадження RSC впливає на розмір JS-бандла?
Типовий результат міграції продуктових сторінок на RSC:
| Компонент | До RSC | Після RSC |
|---|---|---|
| ProductList (дані + рендер) | 24 KB JS | 0 KB JS |
| ProductDetails | 8 KB JS | 0 KB JS |
| AddToCartButton | 2 KB JS | 2 KB JS (клієнтський) |
| Разом на сторінці | 180 KB | 85 KB |
Серверні компоненти не додають JS — вони додають тільки HTML у потік відповіді. Це дає виграш у 2-3 рази за розміром бандла та покращує показники Core Web Vitals (LCP, TTFB). Економія на CDN-трафіку може досягати $2,000 на місяць для середнього інтернет-магазину. Середній проєкт окупається за 4-6 місяців.
Що входить у роботу
Ми пропонуємо комплексне впровадження RSC у ваш проєкт:
- Аудит поточної архітектури: виявлення компонентів, які можна зробити серверними. Розмітка server/client меж: визначення, які компоненти залишаться на клієнті.
- Перенесення data-fetching з API routes у серверні компоненти з прямим доступом до БД. Налаштування Server Actions для мутацій (форми, кошик, улюблене).
- Оптимізація провайдерів: винесення клієнтського коду в ізольовані компоненти, збереження серверного layout. Кешування через
unstable_cacheтаrevalidateTagдля керування свіжістю даних. - Вимірювання JS-бандла до та після, моніторинг серверного рендера. Документація меж та навчання команди (воркшоп по RSC).
Чому обирають нас
Ми — команда з досвідом у Next.js та React. За цей час реалізували десятки проєктів з впровадження RSC для e-commerce, медіа та SaaS. Наші інженери сертифіковані та регулярно виступають на конференціях. Ми гарантуємо результат: зниження JS-бандла на 50–70% та покращення LCP на 40% порівняно з CSSR-підходом.
Терміни реалізації
- Тиждень 1–2: аудит існуючих компонентів, розмітка server/client меж, перенесення data-fetching з API routes у серверні компоненти.
- Тиждень 3: Server Actions для форм та мутацій, заміна REST-викликів на прямі DB-запити.
- Тиждень 4: оптимізація провайдерів (винесення на клієнт без забруднення layout), кешування через
unstable_cache. - Тиждень 5: вимірювання JS bundle до/після, тести, документація меж.
- Тиждень 6: деплой, моніторинг серверного рендера, навчання команди.
Замовте безкоштовний аудит свого проєкту — ми допоможемо оцінити потенціал впровадження RSC та скласти план міграції. Отримайте консультацію інженера з оптимізації продуктивності.







