Przejdź do treści

Jak React Server Components wpływają na SEO i performance?

Zapomnij o ciężkim klienckim JS-ie. Zobacz, jak przejście na architekturę server-first poprawia Core Web Vitals, indeksowanie stron i konwersję.

Maciej Sala

Founder StriveLab

11 min czytaniaOpublikowano 31 marca 2026 (Aktualizacja 16 lipca 2026)

Dla stron marketingowych, blogów, landing page'y i serwisów content-driven liczą się trzy rzeczy: szybkość, indeksowalny HTML i formularze, które działają bez nerwowego czekania na klientowy JavaScript.

Dlaczego server-first w Next.js stał się domyślnym podejściem?

Przez lata webowe aplikacje React przesuwały coraz więcej logiki na stronę klienta. Efektem były rosnące JavaScript, loading spinnery i strony, które bez JS były puste — niewidoczne dla botów wyszukiwarek i niedostępne na słabszych urządzeniach.

W App Routerze punkt ciężkości wrócił na serwer. W praktyce oznacza to, że:

  • Komponenty renderują się na serwerze, jeśli nie oznaczysz ich jako klientowe

  • JavaScript trafia do przeglądarki tylko tam, gdzie jest faktycznie potrzebny (Client Components z dyrektywą 'use client')

  • Formularze i mutacje danych obsługuje serwer bezpośrednio (Server Actions)

  • Strona jest natychmiastowo gotowa do indeksowania, bez czekania na

To nie jest eksperyment do pobawienia się po godzinach. To normalny sposób pracy z App Routerem i dobry punkt startu dla nowych stron content-driven.

React Server Components: jak działają i jak wpływają na wydajność?

Jak React Server Components działają pod maską?

W tradycyjnym modelu React każdy komponent (nawet statyczna stopka czy lista benefitów) był częścią JavaScript bundle'a wysyłanego do przeglądarki. Przeglądarka musiała pobrać JS, zparsować go, wykonać i wyrenderować HTML (hydratacja).

W modelu RSC:

  1. Server Component renderuje się na serwerze do HTML i specjalnego formatu RSC Payload.
  2. Przeglądarka otrzymuje gotowy HTML (natychmiast widoczny) i payload opisujący strukturę.
  3. Client Components (oznaczone 'use client') dołączają interaktywność tylko tam, gdzie jest potrzebna.
  4. Reszta strony nie wymaga ani bajta JavaScriptu, czyli chodzi tutaj o nagłówki, treść tekstową, nawigację i stopkę.
Diagram
Model RSC: serwer renderuje treść do HTML, a JavaScript i hydratacja dotyczą tylko komponentów klienckich.

Wpływ React Server Components na Core Web Vitals

Core Web Vitals nie poprawiają się od samego użycia RSC, tylko wtedy, gdy RSC ograniczają ilość JavaScriptu, skracają czas do HTML-a i stabilizują layout.

Largest Contentful Paint (), czyli czas do wyrenderowania największego elementu widocznego na ekranie. RSC mogą pomóc, bo krytyczny HTML może być gotowy bez czekania na pobranie i wykonanie dużego bundle'a. Cel Google to poniżej 2,5 sekundy.

Interaction to Next Paint (). Opóźnienie między interakcją użytkownika a wizualną reakcją. Mniejszy bundle JavaScript oznacza mniej pracy parsowania i wykonania, co przekłada się na szybszą responsywność. Cel Google: poniżej 200 milisekund.

Cumulative Layout Shift (CLS). Niestabilność layoutu podczas ładowania, gdy układ strony jest znany od początku, łatwiej uniknąć przeskoków podczas hydratacji. Cel Google to poniżej 0,1.

Przykład React Server Components w sekcji blogowej

Rozważmy typową stronę blogową z listą artykułów i sidebarą z kategoriami. W tradycyjnym podejściu client-side:

Code
// ❌ Tradycyjne podejście — wszystko jest Client Component
'use client'
 
import { useState, useEffect } from 'react'
 
export default function BlogPage() {
  const [posts, setPosts] = useState([])
  const [loading, setLoading] = useState(true)
 
  useEffect(() => {
    fetch('https://api.example.com/posts')
      .then((res) => res.json())
      .then((data) => {
        setPosts(data)
        setLoading(false)
      })
  }, [])
 
  if (loading) return <div>Ładowanie...</div> // Puste dla bota
 
  return (
    <div>
      {posts.map((post) => (
        <ArticleCard key={post.id} post={post} />
      ))}
    </div>
  )
}

Bot i użytkownik na start widzą „Ładowanie...” zamiast treści, a LCP cierpi, bo HTML jest pusty do momentu zakończenia fetcha. Cały kod, w tym useState, useEffect i logika pobierania danych, trafia do bundle'a klienta.

W podejściu server-first z RSC:

Code
// ✅ Server-first — Server Component (domyślny)
import { getAllPosts } from '@/lib/posts'
import { ArticleCard } from '@/components/ArticleCard'
import { NewsletterForm } from '@/components/NewsletterForm'
 
export default async function BlogPage() {
  const posts = await getAllPosts()
 
  return (
    <main>
      <h1>Blog</h1>
      <div className="grid grid-cols-1 gap-6 md:grid-cols-3">
        <section className="col-span-2">
          {posts.map((post) => (
            <ArticleCard key={post.slug} post={post} />
          ))}
        </section>
        <aside>
          {/* Client Component tylko ten fragment ma JS */}
          <NewsletterForm />
        </aside>
      </div>
    </main>
  )
}
Code
// components/ArticleCard.tsx — Server Component (brak 'use client')
interface Post {
  slug: string
  title: string
  excerpt: string
  date: string
  readingTime: number
}
 
export function ArticleCard({ post }: { post: Post }) {
  return (
    <article>
      <h2>
        <a href={'/blog/' + post.slug}>{post.title}</a>
      </h2>
      <p>{post.excerpt}</p>
      <time dateTime={post.date}>
        {new Date(post.date).toLocaleDateString('pl-PL')}
      </time>
      <span>{post.readingTime} min czytania</span>
    </article>
  )
}
Code
// components/NewsletterForm.tsx — Client Component (interaktywny)
'use client'
 
import { useActionState } from 'react'
import { subscribeNewsletter } from '@/app/actions'
 
// Akcja przyjmuje (prevState, formData) — to sygnatura wymagana
// przez useActionState
export function NewsletterForm() {
  const [state, formAction, pending] = useActionState(subscribeNewsletter, null)
 
  return (
    <form action={formAction}>
      <h3>Newsletter</h3>
      <input type="email" name="email" placeholder="Twój e-mail" required />
      <button type="submit" disabled={pending}>
        {pending ? 'Zapisuję…' : 'Zapisz się'}
      </button>
      {state?.success && <p>Zapisano!</p>}
      {state?.success === false && <p>Spróbuj ponownie za chwilę.</p>}
    </form>
  )
}

Zwróć uwagę na dwie rzeczy. Po pierwsze, nie ma tu useState na wartość pola — niekontrolowany input z atrybutem name w zupełności wystarcza, a useActionState (standard od React 19) dostarcza stan odpowiedzi i flagę pending bez ręcznej księgowości. Po drugie, akcja jest przekazana bezpośrednio do action formularza, więc React potrafi obsłużyć wysyłkę nawet przed hydratacją, a o tym przeczytasz więcej w sekcji o progressive enhancement. Więcej wzorców z tej rodziny opisuję w artykule o React 19 Actions.

Efekt: przeglądarka może otrzymać kompletny HTML z listą artykułów bez czekania na pobieranie danych i renderowanie po stronie klienta. JavaScript ogranicza się do formularza newslettera. Taka architektura daje dobre warunki do poprawy LCP i ograniczenia ryzyka CLS, ale wynik trzeba potwierdzić pomiarem na realnej stronie: obraz LCP, fonty, CSS, odpowiedź API i hosting nadal mają znaczenie.

Server Actions w Next.js: mniej endpointów dla formularzy

Server Actions to funkcje serwerowe oznaczone dyrektywą 'use server', które możesz wywołać z komponentów React. Nie kasują całego API z projektu, ale zdejmują sporo prostych endpointów: zapis formularza, aktualizacja danych, wysyłka maila, zapis leadu.

Jak Server Actions zmieniają strony marketingowe?

Typowa strona marketingowa ma kilka formularzy: newsletter, kontakt, lead magnet, webinar, kalkulator z zapisem wyniku. W tradycyjnym podejściu każdy z nich łatwo kończył jako osobny endpoint API, walidacja po dwóch stronach i dodatkowy stan ładowania.

Z Server Actions ten sam formularz kontaktowy wygląda tak:

Code
// app/actions.ts
'use server'
 
import { z } from 'zod'
 
const ContactSchema = z.object({
  name: z.string().min(2, 'Imię jest za krótkie'),
  email: z.string().email('Nieprawidłowy adres e-mail'),
  message: z.string().min(10, 'Wiadomość jest za krótka'),
  source: z.string().optional(), // UTM source do trackingu
})
 
export async function submitContact(formData: FormData) {
  const parsed = ContactSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
    message: formData.get('message'),
    source: formData.get('source'),
  })
 
  if (!parsed.success) {
    return {
      success: false,
      errors: parsed.error.flatten().fieldErrors,
    }
  }
 
  // Zapis do bazy / CRM / wysyłka maila
  await saveToDatabase(parsed.data)
  await sendNotificationEmail(parsed.data)
 
  // Tracking konwersji (server-side, nie zależy od JS klienta)
  await trackConversion({
    event: 'contact_form_submit',
    source: parsed.data.source,
  })
 
  return { success: true }
}

Co dostajesz:

  • Niezawodność. Formularz działa nawet gdy JavaScript klienta się nie załaduje (progressive enhancement).
  • Prostszy przepływ. Mniej plików i mniej ręcznie sklejanej komunikacji klient-serwer.
  • Server-side tracking. Zdarzenia konwersji wysyłane z serwera, niezależne od ad blockerów i ograniczeń przeglądarki.
  • Mniejszy bundle. Logika walidacji i zapisu nie trafia do przeglądarki.

Pamiętaj o dwóch sprawach. Pierwszej, Server Action to publiczny endpoint HTTP i Next.js generuje dla niej adres, który można wywołać curl-em, z pominięciem formularza i całego UI. Walidacja przez Zod (jak wyżej) i sprawdzenie uprawnień przy akcjach za logowaniem jest obowiązekowa. Na marginesie, wzorce autoryzacji rozpisuję w artykule o RBAC w Next.js. Druga sprawa, Server Actions służą do mutacji, a nie do odczytu. Każde wywołanie to POST, a akcje wykonują się sekwencyjnie. Pobieranie danych przez akcję blokuje równoległość i omija cache. Dane czytasz w Server Components, akcjami je zmieniasz.

Progressive Enhancement: formularz działa bez JavaScriptu

Server Actions w Next.js działają z natywnym HTML <form action>, więc formularz może działać nawet bez JavaScriptu klienta:

Code
// Ten formularz DZIAŁA bez JavaScript
export function ContactForm() {
  return (
    <form action={submitContact}>
      <input type="text" name="name" required />
      <input type="email" name="email" required />
      <textarea name="message" required />
      <button type="submit">Wyślij</button>
    </form>
  )
}

Użytkownik na wolnym łączu mobilnym wysyła formularz, zanim załaduje się JavaScript. Każda sekunda opóźnienia formularza to utracone leady.

progressive enhancement

działa tylko wtedy, gdy do action trafia bezpośrednio Server Action (jak wyżej) albo akcja opakowana w useActionState (jak w formularzu newslettera). Wtedy React potrafi odtworzyć wysyłkę sprzed hydratacji. Gdy podłączysz własny kliencki handler (action={async (fd) => {...}}) albo onSubmit z preventDefault, formularz bez JavaScriptu jest martwy, kod wygląda niemal identycznie, a gwarancja znika po cichu.

RSC nie naprawiają waterfalli: pobieraj dane równolegle i świadomie je cache'uj

Server Component eliminuje klientowy useEffect, ale nie skraca automatycznie czasu odpowiedzi serwera. Jeśli jedna funkcja czeka na drugą, użytkownik nadal czeka na sumę obu opóźnień. Niezależne dane uruchamiaj równolegle, a zależne pobieraj dopiero po otrzymaniu potrzebnego identyfikatora:

Code
export default async function ProductPage({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
 
  const [product, relatedProducts] = await Promise.all([
    getProduct(slug),
    getRelatedProducts(slug),
  ])
 
  return <ProductDetails product={product} relatedProducts={relatedProducts} />
}

Druga decyzja to cache. Dane publiczne, które zmieniają się rzadko, warto cache'ować i unieważniać po mutacji. Dane zależne od sesji, koszyka czy uprawnień muszą pozostać per żądanie. W Next.js 16 Cache Components trzeba włączyć przez cacheComponents: true; dyrektywa 'use cache' może objąć funkcję pobierającą dane albo cały komponent. Nie dodawaj cache'u na ślepo: błędny klucz lub zbyt długie życie wpisu potrafi pokazać cenę, dostępność albo dane użytkownika w nieaktualnej wersji.

W praktyce optymalizację oceniaj w tej kolejności: najpierw usuń sekwencyjne oczekiwanie, potem wybierz cache dla danych publicznych, a wolne fragmenty odizoluj przez <Suspense>. RSC są fundamentem tego układu, nie zamiennikiem dla dobrej strategii danych.

Streaming i Suspense w Next.js: szybszy feedback dla użytkownika

Next.js z RSC wspiera streaming HTML. Serwer wysyła gotowe fragmenty strony w miarę ich renderowania, zamiast czekać aż cała strona będzie gotowa. W połączeniu z React Suspense pozwala to na natychmiastowe wyświetlenie szkieletu strony z progresywnym dociąganiem treści:

Code
import { Suspense } from 'react'
 
export default async function ProductPage({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
 
  return (
    <main>
      {/* Natychmiast — statyczna sekcja */}
      <ProductHero slug={slug} />
 
      {/* Streaming — ładuje się asynchronicznie */}
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews slug={slug} />
      </Suspense>
 
      <Suspense fallback={<RecommendationsSkeleton />}>
        <RelatedProducts slug={slug} />
      </Suspense>
    </main>
  )
}

Z perspektywy SEO i UX:

  • Użytkownik widzi treść natychmiast (główna sekcja produktu),
  • LCP jest niski, bo najważniejszy element jest renderowany jako pierwszy,
  • Sekcje drugorzędne (recenzje, polecane) dociągają się płynnie,
  • Bot widzi kompletny HTML po zakończeniu streamingu,

Wariantem tego samego mechanizmu jest plik loading.tsx. To na poziomie całej trasy, więc Next.js pokazuje go natychmiast przy nawigacji, a strona streamuje się pod spodem. Pamiętaj, że loading.tsx jest dla całych podstron, a ręczny <Suspense> tam, gdzie chcesz wypuścić główną treść od razu i dociągać tylko sekcje drugorzędne. Fallback niech rezerwuje docelowe wymiary (skeleton, a nie spinner w pustym divie) inaczej streaming, który miał poprawić UX, zacznie generować przeskoki layoutu i psuć CLS. Dodam, że przez nieopatrzność, miałem taką sytuację na stronie StriveLab.

Partial Prerendering w Next.js: hybryda statycznego i dynamicznego renderowania

Partial Prerendering (PPR) łączy statyczny HTML z dynamicznymi komponentami w jednym żądaniu HTTP. Statyczna „powłoka" strony (nawigacja, footer, layout) jest serwowana od razu, a dynamiczne fragmenty (personalizowany content, dane real-time) doładowują się przez streaming.

To bardzo naturalny kierunek dla stron marketingowych:

  • Strony katalogowe — statyczny layout + dynamiczne ceny i dostępność
  • Landing page z personalizacją — statyczna struktura + dynamiczny headline/CTA na podstawie UTM
  • Blog z komentarzami — statyczny artykuł + dynamiczna sekcja komentarzy

Server Component czy Client Component: strategia wyboru

Najpierw zdecyduj, co zostaje Server Componentem (domyślne), a co trafia do Client Componentu (oznaczonego 'use client'). Zasada ogólna:

Server Component (domyślny) stosuj do:

  • Treści statycznych (nagłówki, akapity, listy, karty artykułów)

  • Pobierania danych (fetch z bazy, CMS, API)
  • Renderowania markdown/MDX
  • Komponentów, które nie wymagają interaktywności
  • SEO-critical content (meta tagi, structured data, treść indeksowalna)

Client Component ('use client') stosuj do:

  • Interaktywnych formularzy z walidacją real-time
  • Stanowych komponentów (tabs, accordiony, modalne, dropdown)

  • Komponentów korzystających z Browser API (localStorage, geolocation, clipboard)

  • Animacji reagujących na interakcje użytkownika
  • Widgetów third-party (mapy, chaty, embedy)

Zasada kciuka: jeśli komponent nie potrzebuje useState, useEffect ani obsługi zdarzeń przeglądarki — powinien być Server Component.

Granica server/client w praktyce: kompozycja i serializacja

Dyrektywa 'use client' działa na zasadzie propagacji, czyli każdy komponent importowany przez komponent kliencki automatycznie staje się częścią bundle'a przesyłanego do przeglądarki. Nawet jeśli importujesz wyłącznie statyczną treść, całość poddrzewa importów traci status serwerowy. Nierozważne umieszczenie tej dyrektywy w layoutcie może przez to nieumyślnie przekształcić architekturę server-first w klasyczne SPA.

Rozwiązaniem jest kompozycja. Zamiast importować treść serwerową bezpośrednio do komponentu klienckiego, przekazuj ją jako children:

Code
// ❌ Import wciąga ArticleContent do bundle'a klienta
'use client'
import { useState } from 'react'
import { ArticleContent } from './ArticleContent'
 
export function Accordion() {
  const [open, setOpen] = useState(false)
  return (
    <div>
      <button onClick={() => setOpen(!open)}>Szczegóły</button>
      {open && <ArticleContent />}
    </div>
  )
}
Code
// ✅ Treść przychodzi z serwera jako children — JS ma tylko accordion
'use client'
import { useState } from 'react'
 
export function Accordion({ children }: { children: React.ReactNode }) {
  const [open, setOpen] = useState(false)
  return (
    <div>
      <button onClick={() => setOpen(!open)}>Szczegóły</button>
      {open && children}
    </div>
  )
}
Code
// Strona (Server Component) składa jedno z drugim
<Accordion>
  <ArticleContent /> {/* renderuje się na serwerze */}
</Accordion>

Interaktywne komponenty projektuj jako liście drzewa, czyli wąskie wyspy stanu, do których serwer wlewa gotową treść. Druga zasada dotyczy propsów, w której wszystko, co przechodzi z serwera do klienta, musi być serializowalne (obiekty, tablice, stringi, liczby, daty). Funkcji, instancji klas ani klienta bazy nie przekażesz, choć jest wyjątek: Server Actions wolno przekazywać jako propsy, bo React zamienia je w bezpieczne referencje do endpointów. Ile ta dyscyplina realnie waży, sprawdzisz analizą bundle'a, a metodę opisuję w artykule o bundle analysis w Next.js.

Wpływ React Server Components i Server Actions na SEO

Server-first daje SEO lepszy punkt startu, bo crawler dostaje więcej gotowej treści w HTML i mniej zależy od wykonania JavaScriptu.

Core Web Vitals

  • LCP może się poprawić, bo krytyczny HTML jest dostępny wcześniej i nie czeka na duży bundle JavaScript

  • INP ma lepsze warunki, gdy wysyłasz mniej kodu do klienta i zostawiasz przeglądarce mniej pracy

  • CLS łatwiej utrzymać w ryzach, gdy layout jest kompletny i przewidywalny od początku

Crawlability

  • Treść może być dostępna w HTML źródłowym bez czekania na fetch po stronie klienta

  • Boty wyszukiwarek i AI crawlery dostają stabilniejszy HTML na start

  • Structured data (JSON-LD) może być generowane po stronie serwera z pełnymi danymi

Czas indeksacji

  • Strony z kompletnym HTML mają mniej zależności od drugiej fali renderowania JavaScriptu

  • Nowe treści mają większą szansę zostać poprawnie odczytane już z początkowego HTML-a

  • Mniej rozjazdów między HTML-em serwera a renderem klienta oznacza mniej ryzyka błędów indeksacji

Migracja z Pages Router na App Router przy server-first Next.js

Jeśli masz istniejący projekt Next.js na Pages Router, migracja na App Router (i tym samym na RSC) jest procesem przyrostowym:

  1. Zacznij od nowych stron. Nowe sekcje buduj w App Router (app/ directory), istniejące zostaw w Pages Router (pages/)
  2. Migruj strony statyczne najpierw. Blog, about, polityka prywatności — te strony najłatwiej konwertować na Server Components
  3. Wydziel Client Components. Zidentyfikuj interaktywne fragmenty i wydziel je do osobnych plików z 'use client'
  4. Zastąp API routes Server Actions. Formularze kontaktowe, zapis na newsletter, proste mutacje
  5. Zmigruj data fetching. getStaticProps / getServerSideProps zamień na async Server Components z bezpośrednim fetch lub dostępem do bazy

Next.js wspiera oba routery jednocześnie, więc migracja może być rozłożona w czasie bez ryzyka regresji.

Jeśli rozwijasz ten temat dalej, zobacz też App Router czy Pages Router — co wybrać? i React 19 Actions.

Audyt techniczny i optymalizacja pod kątem SEO i GEO.
Audyt techniczny SEO

Często zadawane pytania

Czym są React Server Components?

React Server Components (RSC) to komponenty renderowane na serwerze, a ich kod nie trafia do przeglądarki, więc użytkownik dostaje gotowy HTML i mniejszy bundle JavaScript. To dobry układ dla stron contentowych, bo treść jest dostępna wcześniej i mniej zależy od hydratacji.

Jak Server Components wpływają na SEO?

Największa korzyść to więcej treści trafiającej do początkowego HTML-a, a mniej pracy zostaje po stronie przeglądarki. To pomaga przy LCP, zmniejsza zależność indeksacji od renderowania JavaScriptu i daje crawlerom lepszy punkt startu. Nie jest to magiczny wzrost pozycji, tylko lepsza architektura pod szybkie, indeksowalne strony.

Czym są Server Actions w Next.js?

Server Actions to funkcje serwerowe oznaczone dyrektywą use server. Możesz użyć ich do formularzy, mutacji danych i wysyłki maili bez budowania osobnego endpointu API dla każdej prostej akcji. Z natywnym HTML form formularz może działać nawet wtedy, gdy JavaScript klienta jeszcze się nie załadował.

Kiedy używać Server Component, a kiedy Client Component?

Jeśli komponent nie potrzebuje useState, useEffect ani obsługi zdarzeń przeglądarki, zostaw go jako Server Component. Client Component (use client) dawaj tam, gdzie masz realną interakcję: stan, Browser API, animacje po kliknięciu, modale, widgety third-party. Im mniej niepotrzebnego use client, tym lżejsza strona.

Czy mogę migrować istniejący projekt na Server Components?

Tak, przyrostowo. Next.js wspiera Pages Router i App Router jednocześnie. Nowe strony buduj w App Router, istniejące migruj stopniowo i zacznij od stron statycznych (blog, about), wydziel Client Components, zastąp API routes Server Actions, a getStaticProps/getServerSideProps zamień na async Server Components.

Czy Server Actions nadają się do pobierania danych?

Nie, bo to narzędzie do mutacji. Każde wywołanie Server Action to żądanie POST, a Next.js wykonuje akcje sekwencyjnie, więc użycie ich do odczytu danych blokuje równoległość i psuje cache. Dane pobieraj w Server Components (bezpośredni dostęp do bazy lub fetch), a Server Actions zostaw formularzom i zmianom stanu.

Czy Server Action jest bezpieczna sama z siebie?

Nie. Każda Server Action dostaje własny publiczny endpoint HTTP i można ją wywołać bez klikania w formularz. Walidację danych wejściowych i sprawdzenie uprawnień rób wewnątrz akcji, tak samo jak w klasycznym endpoincie API — ukrycie przycisku w UI niczego nie chroni.

Dlaczego moje propsy nie przechodzą do Client Componentu?

Propsy przekraczające granicę serwer–klient muszą być serializowalne: obiekty, tablice, stringi, liczby, daty. Funkcje, instancje klas czy klienty bazy danych nie przejdą, a jedynym wyjątkiem są Server Actions, które można przekazywać jako propsy. Jeśli komponent kliencki potrzebuje logiki z serwera, przekaż wynik jej działania albo gotowy JSX przez children, nie samą funkcję.

Ile JavaScriptu realnie oszczędzam z RSC?

Zależy od architektury i granic między komponentami serwerowymi a klienckimi. W serwisach opartych o treść oszczędność bywa znacząca, bo logika renderowania całej treści nie trafia do klienta. Konkretny wynik zawsze potwierdź bundle analyzem i pomiarem Core Web Vitals.

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwijam własne projekty.

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy na temat Next.js

Czytaj dalej

Zobacz więcej wpisów
App Router czy Pages Router — co wybrać?

Next.js 13 wprowadził App Router , czyli nowy sposób budowania aplikacji oparty na React Server Components . Ale Pages Router nigdzie nie zniknął. Dwa routery, dwa podejścia, jedna decyzja do podjęcia na starcie projektu.

Maciej Sala

Maciej Sala

Founder StriveLab

Astro vs Next.js w 2026: Porównanie frameworków

Astro czy Next.js? Wybór frameworka musi być dokładnie przemyślany, zanim pojawi się pierwszy commit. Jeśli stoisz przed takim właśnie wyborem, w tym artykule staram się wykazać, w jakich obszarach najlepiej sprawdza się Astro , a w jakich będzie dominował Next.js .

Maciej Sala

Maciej Sala

Founder StriveLab

Pages Router do App Router: Migracja w Next.js

Masz działający projekt na Pages Router i chcesz wejść w Server Components, streaming albo Server Actions. Tylko że przepisanie wszystkiego naraz to proszenie się o regresję, więc tego lepiej nie robić. Rozwiązaniem jest Pages i App Router obok siebie w jednym repozytorium i przenoszenie strona po stronie, potem testy i bezpieczne deploye.

Maciej Sala

Maciej Sala

Founder StriveLab