Реалізація React Server Components для веб-застосунку

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація React Server Components для веб-застосунку
Складна
~5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація React Server Components для веб-застосунку

React Server Components (RSC) — це не SSR у звичайному розумінні. Це принципово нова модель компонентів: серверні компоненти виконуються тільки на сервері, ніколи не гідруються на клієнті, не додають ні байта у JS-бандл. Вони можуть звертатися до бази даних прямо, читати файлову систему, використовувати серверні секрети — все це без API-шару.

RSC міняє не фреймворк — RSC міняє те, як ви думаєте про компоненти.

Server Components vs 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 — теж серверний компонент */}
          <ProductCard product={product} />
          {/* AddToCartButton — клієнтський компонент */}
          <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 бібліотеки
  • Рендерити інші серверні та клієнтські компоненти

Server Components НЕ МОЖУТЬ:

  • Використовувати useState, useEffect, useContext
  • Обробляти браузерні події (onClick, onChange)
  • Використовувати браузерні API (localStorage, window)
  • Приймати функції як пропси (не сериалізується)

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>
  );
}

Оптимізація: контекст та провайдери

Типова помилка — оборачувати весь застосунок у 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>
  );
}

Паттерн: передача серверних даних через пропси

// Серверний компонент передає дані клієнтському
export default async function ProductPage({ params }: Props) {
  const product = await getProduct(params.id);
  const recommendations = await getRecommendations(product.categoryId);

  return (
    <div>
      {/* Серверні компоненти рендерять статичний контент */}
      <ProductDetails product={product} />
      <ProductImages images={product.images} />

      {/* Клієнтський отримує тільки потрібні дані — не весь product */}
      <ProductCarousel
        items={recommendations.map(r => ({ id: r.id, name: r.name, image: r.images[0]?.url }))}
      />
    </div>
  );
}

Розмір бандла до та після 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 у потік відповіді.

Строки реалізації

  • Тиждень 1–2: аудит існуючих компонентів, розмітка server/client меж, переміщення data-fetching з API routes у серверні компоненти
  • Тиждень 3: Server Actions для форм та мутацій, заміна REST-викликів на прямі DB-запити
  • Тиждень 4: оптимізація провайдерів (вихід на клієнт без забруднення layout), кешування через unstable_cache
  • Тиждень 5: вимірювання JS bundle до/після, тести (jest-environment для RSC), документація меж
  • Тиждень 6: розгортання, моніторинг серверного рендеру, навчання команди