# React 19: Server Components w produkcji - kompletny przewodnik > Opanowanie Server Components w React 19 w warunkach produkcyjnych. Architektura, wzorce, streaming, cache i optymalizacje dla wydajnych aplikacji. - Published: 2026-01-08 - Updated: 2026-04-06 - Author: SharpSkill - Tags: react 19, server components, rsc, performance, next.js - Reading time: 14 min --- Server Components to najbardziej znaczaca ewolucja w React od czasu wprowadzenia Hooks. W React 19 ta architektura dojrzala i jest gotowa do uzytku produkcyjnego, umozliwiajac wykonywanie komponentow bezposrednio na serwerze przy jednoczesnym zachowaniu interaktywnosci po stronie klienta. > **Wymagania wstepne** > > Ten przewodnik zaklada znajomosc React oraz Next.js App Router. Przyklady wykorzystuja Next.js 14+, ktory natywnie implementuje React Server Components. ## Understanding the Server Components Architecture Server Components (RSC) wprowadzaja nowy paradygmat: czesc komponentow dziala wylacznie na serwerze, inne na kliencie, a oba typy moga wspolistniec w tym samym drzewie komponentow. Taki podzial znaczaco optymalizuje wydajnosc, redukujac ilosc JavaScriptu wysylanego do przegladarki. Podstawowa idea opiera sie na fakcie, ze wiele komponentow nie potrzebuje interaktywnosci. Komponent wyswietlajacy liste artykulow z bazy danych moze dzialac calkowicie po stronie serwera. Jedynie interaktywne elementy (przyciski, formularze, animacje) wymagaja JavaScriptu po stronie klienta. ```tsx // app/articles/page.tsx // This component runs only on the server // No JavaScript is sent to the client for this component import { getArticles } from '@/lib/articles' import ArticleCard from './ArticleCard' import LikeButton from './LikeButton' // async/await directly in the component // Only possible with Server Components export default async function ArticlesPage() { // Direct database call (no REST API needed) const articles = await getArticles() return (

Recent Articles

{articles.map((article) => ( // ArticleCard is also a Server Component {/* LikeButton is a Client Component (interactive) */} ))}
) } ``` Dyrektywa `"use client"` jawnie oznacza komponenty, ktore wymagaja JavaScriptu w przegladarce. ```tsx // app/articles/LikeButton.tsx 'use client' // useState and interactive hooks require "use client" import { useState, useTransition } from 'react' import { likeArticle } from '@/actions/articles' interface LikeButtonProps { articleId: string initialLikes?: number } export default function LikeButton({ articleId, initialLikes = 0 }: LikeButtonProps) { // Local state for optimistic UI const [likes, setLikes] = useState(initialLikes) const [isPending, startTransition] = useTransition() const handleLike = () => { // Immediate optimistic update setLikes((prev) => prev + 1) // Server Action to persist startTransition(async () => { await likeArticle(articleId) }) } return ( ) } ``` Ta architektura znaczaco redukuje rozmiar paczki JavaScript: do klienta trafia jedynie kod `LikeButton`, a nie `ArticlesPage` czy `ArticleCard`. ## Server/Client Composition Patterns Kompozycja miedzy Server Components a Client Components podlega scislym regulom. Server Component moze importowac i renderowac Client Components, ale w druga strone nie jest to bezposrednio mozliwe. Aby przekazac tresc serwerowa do komponentu klienckiego, wzorzec `children` stanowi rozwiazanie. ```tsx // components/InteractiveWrapper.tsx 'use client' import { useState, ReactNode } from 'react' interface InteractiveWrapperProps { children: ReactNode expandable?: boolean } // Client Component that wraps server content export function InteractiveWrapper({ children, expandable = false }: InteractiveWrapperProps) { const [isExpanded, setIsExpanded] = useState(!expandable) if (!expandable) { return
{children}
} return (
{isExpanded && (
{/* children can contain Server Components */} {children}
)}
) } ``` ```tsx // app/dashboard/page.tsx // Server Component using the client wrapper import { InteractiveWrapper } from '@/components/InteractiveWrapper' import { getStats, getRecentActivity } from '@/lib/dashboard' export default async function DashboardPage() { // Parallel server-side requests const [stats, activity] = await Promise.all([ getStats(), getRecentActivity() ]) return (
{/* Stats in an expandable wrapper */} {/* This content is rendered server-side then passed to client */}
{/* Recent activity */}
) } ``` Ten wzorzec umozliwia laczenie interaktywnosci klienta z danymi renderowanymi po stronie serwera bez duplikowania logiki. ## Data Fetching and Caching > **Rozszerzony fetch w React** > > React 19 automatycznie rozszerza natywne API `fetch`, dodajac deduplikacje i cache. Identyczne zapytania w ramach tego samego renderowania sa wykonywane tylko raz. Pobieranie danych w Server Components odbywa sie bezposrednio za pomoca `async/await`. React automatycznie obsluguje deduplikacje identycznych zapytan. ```tsx // lib/api.ts // Centralized request configuration with caching const API_BASE = process.env.API_URL // Request with time-based revalidation export async function getProducts() { const response = await fetch(`${API_BASE}/products`, { // Revalidate every hour next: { revalidate: 3600 } }) if (!response.ok) { throw new Error('Failed to fetch products') } return response.json() } // Request without cache (real-time data) export async function getCurrentUser() { const response = await fetch(`${API_BASE}/me`, { // No cache, always fresh cache: 'no-store' }) if (!response.ok) { return null } return response.json() } // Request with tag for targeted invalidation export async function getProduct(id: string) { const response = await fetch(`${API_BASE}/products/${id}`, { next: { tags: [`product-${id}`], revalidate: 3600 } }) if (!response.ok) { throw new Error('Product not found') } return response.json() } ``` W przypadku bezposredniego dostepu do bazy danych (Prisma, Drizzle), React cache z `unstable_cache` oferuje te same mozliwosci. ```tsx // lib/db-queries.ts import { unstable_cache } from 'next/cache' import { prisma } from '@/lib/prisma' // Cache categories (rarely modified) export const getCategories = unstable_cache( async () => { return prisma.category.findMany({ orderBy: { name: 'asc' } }) }, ['categories'], // Cache key { revalidate: 86400, // 24 hours tags: ['categories'] } ) // Cache products by category export const getProductsByCategory = unstable_cache( async (categoryId: string) => { return prisma.product.findMany({ where: { categoryId }, include: { images: true }, orderBy: { createdAt: 'desc' } }) }, ['products-by-category'], { revalidate: 3600, tags: ['products'] } ) // Cache invalidation after mutation export async function createProduct(data: ProductInput) { const product = await prisma.product.create({ data }) // Invalidate related caches revalidateTag('products') return product } ``` ## Streaming and Suspense for Optimal UX Streaming umozliwia progresywne wysylanie HTML do przegladarki, natychmiastowe wyswietlanie dostepnych czesci podczas ladowania pozostalych. W polaczeniu z Suspense mechanizm ten drastycznie poprawia Time to First Byte (TTFB) oraz postrzegane doswiadczenie uzytkownika. ```tsx // app/product/[id]/page.tsx import { Suspense } from 'react' import { getProduct } from '@/lib/products' import ProductDetails from './ProductDetails' import ProductReviews from './ProductReviews' import RecommendedProducts from './RecommendedProducts' import { Skeleton } from '@/components/ui/Skeleton' interface ProductPageProps { params: { id: string } } export default async function ProductPage({ params }: ProductPageProps) { // This request blocks initial render const product = await getProduct(params.id) return (
{/* Immediate render with product data */} {/* Reviews load via streaming */}

Customer Reviews

}> {/* This async component will be streamed */}
{/* Recommendations too */}

Similar Products

}>
) } // Skeleton for reviews function ReviewsSkeleton() { return (
{[1, 2, 3].map((i) => (
))}
) } ``` ```tsx // app/product/[id]/ProductReviews.tsx // Async Server Component that will be streamed import { getProductReviews } from '@/lib/reviews' interface ProductReviewsProps { productId: string } export default async function ProductReviews({ productId }: ProductReviewsProps) { // This request may take time // Component will be streamed when complete const reviews = await getProductReviews(productId) if (reviews.length === 0) { return (

No reviews yet. Be the first to share your thoughts!

) } return (
{reviews.map((review) => (
{review.author} {'★'.repeat(review.rating)}{'☆'.repeat(5 - review.rating)}

{review.content}

))}
) } ``` Przeglądarka najpierw otrzymuje szkielet HTML ze skeletonami, a nastepnie rzeczywista tresc jest stopniowo wstrzykiwana przez streaming. ## Server Actions for Mutations Server Actions umozliwiaja wykonywanie kodu serwerowego z poziomu komponentow klienckich bez tworzenia tras API. Takie podejscie znaczaco upraszcza obsluge mutacji. ```tsx // actions/cart.ts 'use server' import { revalidatePath } from 'next/cache' import { cookies } from 'next/headers' import { prisma } from '@/lib/prisma' import { getCurrentUser } from '@/lib/auth' // Action to add to cart export async function addToCart(productId: string, quantity: number = 1) { const user = await getCurrentUser() if (!user) { // Return structured error return { error: 'Login required', code: 'UNAUTHORIZED' } } try { // Check stock const product = await prisma.product.findUnique({ where: { id: productId } }) if (!product || product.stock < quantity) { return { error: 'Insufficient stock', code: 'OUT_OF_STOCK' } } // Add or update cart item await prisma.cartItem.upsert({ where: { cartId_productId: { cartId: user.cartId, productId } }, update: { quantity: { increment: quantity } }, create: { cartId: user.cartId, productId, quantity } }) // Invalidate cart page cache revalidatePath('/cart') return { success: true, message: 'Product added to cart' } } catch (error) { console.error('Add to cart error:', error) return { error: 'An error occurred', code: 'SERVER_ERROR' } } } // Action to remove from cart export async function removeFromCart(itemId: string) { const user = await getCurrentUser() if (!user) { return { error: 'Login required' } } await prisma.cartItem.delete({ where: { id: itemId, cart: { userId: user.id } } }) revalidatePath('/cart') return { success: true } } ``` ```tsx // components/AddToCartButton.tsx 'use client' import { useTransition } from 'react' import { addToCart } from '@/actions/cart' import { toast } from '@/components/ui/toast' interface AddToCartButtonProps { productId: string } export function AddToCartButton({ productId }: AddToCartButtonProps) { const [isPending, startTransition] = useTransition() const handleClick = () => { startTransition(async () => { const result = await addToCart(productId) if (result.error) { toast.error(result.error) return } toast.success(result.message) }) } return ( ) } ``` > **Walidacja po stronie serwera** > > Dane w Server Actions powinny byc zawsze walidowane po stronie serwera. Walidacje klienckie mozna obejsc. Nalezy uzyc Zod lub podobnej biblioteki do solidnej walidacji. ## Error Handling and Error Boundaries React 19 usprawnia obsluge bledow w Server Components. Error Boundaries dzialaja tak samo jak w przypadku Client Components. ```tsx // app/products/error.tsx 'use client' // Error Boundary for /products segment interface ErrorProps { error: Error & { digest?: string } reset: () => void } export default function ProductsError({ error, reset }: ErrorProps) { return (

Loading Error

Unable to load products. Please try again.

{/* Display digest for debugging */} {error.digest && (

Reference: {error.digest}

)}
) } ``` ```tsx // app/products/loading.tsx // Loading UI during initial load export default function ProductsLoading() { return (
{[1, 2, 3, 4, 5, 6].map((i) => (
))}
) } ``` Do bardziej szczegolowej obslugi bledow w konkretnych komponentach mozna uzyc komponentu `ErrorBoundary` bezposrednio. ```tsx // components/ErrorBoundary.tsx 'use client' import { Component, ReactNode } from 'react' interface Props { children: ReactNode fallback?: ReactNode } interface State { hasError: boolean error?: Error } export class ErrorBoundary extends Component { constructor(props: Props) { super(props) this.state = { hasError: false } } static getDerivedStateFromError(error: Error): State { return { hasError: true, error } } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { // Log error to monitoring service console.error('ErrorBoundary caught:', error, errorInfo) } render() { if (this.state.hasError) { return this.props.fallback || (

An error occurred

) } return this.props.children } } ``` ## Production Performance Optimizations Kilka technik pozwala na optymalizacje wydajnosci Server Components w srodowisku produkcyjnym. ```tsx // app/layout.tsx import { Suspense } from 'react' import { headers } from 'next/headers' // Preload critical data export const dynamic = 'force-dynamic' export default async function RootLayout({ children }: { children: React.ReactNode }) { return ( {/* Header streamed independently */} }>
{children}
{/* Footer can be static */}