SEO dla Next.js, React i Nuxt

Vadim Kravcenko
Vadim Kravcenko
· 18 min read

TL;DR: Frameworki JavaScript domyślnie psują SEO. Renderowanie po stronie klienta oznacza, że wyszukiwarki widzą pustą stronę — a crawlery AI w ogóle nie uruchamiają JavaScriptu. Next.js (z Server Components i Metadata API) to najlepsza opcja dla SEO w tej chwili. Nuxt jest tuż za nim dla zespołów pracujących z Vue. Czyste aplikacje SPA w React nigdy nie powinny być używane do stron, które chcesz indeksować. Ten przewodnik zawiera konkretny kod, konkretne błędy i konkretne rozwiązania. Bez lania wody.

Fundamentalny problem: JavaScript i wyszukiwarki nie są przyjaciółmi

Schemat przetwarzania JavaScriptu przez Googlebota pokazujący fazy crawlowania, renderowania i indeksowania
Jak Googlebot przetwarza JavaScript: strony przechodzą oddzielne fazy crawlowania, renderowania i indeksowania, co może opóźniać odkrywanie treści. Źródło: Google Search Central

Zasada: Jeśli URL ma się pojawiać w wynikach wyszukiwania lub być cytowany przez AI, musi dostarczać kompletny HTML w pierwszej odpowiedzi. Tyle. „Ale Google potrafi renderować JavaScript” — tak, w końcu, zawodnie, po kolejce, która może trwać dni, na infrastrukturze, którą Google utrzymuje ogromnym kosztem wyłącznie dlatego, że tylu deweloperów wysyła puste powłoki HTML i oczekuje, że wyszukiwarka wykona za nich robotę renderowania. A crawlery AI nawet nie próbują.

ISR jest... właściwie, cofnę się o krok. Kiedyś uważałem, że ISR to wyraźny zwycięzca nad SSR w większości przypadków. Po tym, jak obserwowałem, jak błędy rewalidacji powodowały serwowanie nieaktualnych treści przez tygodnie na kilku witrynach klientów, jestem mniej pewny. Na stronie e-commerce jednego klienta ISR serwował nieaktualne ceny przez 6 godzin, bo rewalidacja po cichu zawiodła — żadnego błędu w logach, żadnego alertu, po prostu złe ceny pokazywane klientom i crawlerom. ISR jest świetny, kiedy działa. Kiedy nie działa, debugowanie problemów z nieaktualnym cachem to prawdziwa męczarnia. SSR jest bardziej przewidywalny, nawet jeśli wolniejszy.

SEO w Next.js: dogłębny przewodnik

Next.js dostaje najdłuższą sekcję, bo na to zasługuje. To najpopularniejszy framework dla Reacta, a odkąd App Router stał się domyślny w Next.js 13+, jest najlepszym frameworkiem JavaScript pod kątem SEO. Nie o włos — znacząco lepszym od alternatyw.

Jeśli zaczynasz nowy projekt w 2026 roku i SEO ma dla Ciebie znaczenie, użyj Next.js z App Routerem. Taka jest rekomendacja. Reszta tej sekcji wyjaśnia dlaczego.

App Router vs Pages Router — implikacje dla SEO

Next.js ma dwa systemy routingu. App Router (wprowadzony w Next.js 13, stabilny od wersji 14+) i starszy Pages Router. Oba mogą dawać dobre wyniki SEO, ale App Router jest lepszy praktycznie pod każdym względem, który ma znaczenie dla wyszukiwarek:

  • Server Components domyślnie — komponenty renderują się na serwerze, chyba że jawnie dodasz 'use client'. Mniej wysłanego JavaScriptu oznacza szybsze strony i kompletny HTML dla crawlerów.
  • Streaming SSR — serwer zaczyna wysyłać HTML natychmiast, zanim pobierze wszystkie dane. TTFB się poprawia. Googlebot szybciej dostaje treść.
  • Wbudowane Metadata API — typowane generowanie metadanych per ścieżka. Koniec z zewnętrznymi paczkami do obsługi meta tagów.
  • Zagnieżdżone layouty — współdzielone elementy UI zachowują się między nawigacjami bez ponownego renderowania, co poprawia Core Web Vitals.

Pages Router wciąż działa dobrze. Jeśli masz dużą istniejącą bazę kodu na nim, nie panikuj z migracją.

Ale dla nowych projektów? App Router, bez dyskusji.

Metadata API (to jest najważniejszy element)

Przed App Routerem SEO w Next.js oznaczało instalowanie next-seo i ręczne podpinanie meta tagów. Metadata API — które moim zdaniem jest jedną z najlepszych rzeczy w App Routerze, mimo moich narzekań na ból migracji — obsługuje to elegancko jako natywna funkcja frameworka.

Statyczne metadane dla strony:

// app/about/page.tsx
import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'About Us | Your Company',
  description: 'We build tools that make SEO automatic.',
  openGraph: {
    title: 'About Us',
    description: 'We build tools that make SEO automatic.',
    type: 'website',
  },
  alternates: {
    canonical: 'https://example.com/about',
  },
  robots: {
    index: true,
    follow: true,
  },
}

Dynamiczne metadane dla stron z parametrami (posty blogowe, produkty itd.):

// app/blog/[slug]/page.tsx
import { Metadata } from 'next'

type Props = { params: { slug: string } }

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const post = await getPost(params.slug)

  return {
    title: `${post.title} | Your Blog`,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: 'article',
      publishedTime: post.publishedAt,
      authors: [post.author.name],
      images: [{ url: post.ogImage, width: 1200, height: 630 }],
    },
    alternates: {
      canonical: `https://example.com/blog/${params.slug}`,
    },
  }
}

export default async function BlogPost({ params }: Props) {
  const post = await getPost(params.slug)

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

To jest Server Component. Brak dyrektywy 'use client'. Wykonuje się w całości na serwerze. HTML, który dociera do Googlebota, zawiera pełną treść artykułu i wszystkie meta tagi. Zero renderowania po stronie JavaScriptu.

Błąd, który widzę ciągle: deweloperzy umieszczają pobieranie danych w komponencie 'use client', co oznacza, że treść ładuje się przez JavaScript po wysłaniu początkowego HTML. Googlebot dostaje pustą stronę. Serio. Przenieś pobieranie danych do Server Components lub generateMetadata. A jeśli nie jesteś pewien, czy komponent jest serwerowy czy kliencki, sprawdź dyrektywę 'use client' na górze pliku — jeśli tam jest, nic z tego drzewa komponentów nie znajdzie się w początkowej odpowiedzi HTML.

Generowanie mapy witryny

App Router w Next.js ma natywne wsparcie dla map witryny. Stwórz plik app/sitemap.ts, a wygeneruje się automatycznie pod /sitemap.xml:

// app/sitemap.ts
import { MetadataRoute } from 'next'

export default async function sitemap(): MetadataRoute.Sitemap {
  const posts = await getAllPosts()
  const products = await getAllProducts()

  const postEntries = posts.map((post) => ({
    url: `https://example.com/blog/${post.slug}`,
    lastModified: new Date(post.updatedAt),
    changeFrequency: 'weekly' as const,
    priority: 0.7,
  }))

  const productEntries = products.map((product) => ({
    url: `https://example.com/products/${product.slug}`,
    lastModified: new Date(product.updatedAt),
    changeFrequency: 'daily' as const,
    priority: 0.8,
  }))

  return [
    {
      url: 'https://example.com',
      lastModified: new Date(),
      changeFrequency: 'daily',
      priority: 1,
    },
    ...postEntries,
    ...productEntries,
  ]
}

Dla witryn z ponad 50 000 adresów URL użyj generateSitemaps() do stworzenia wielu plików mapy witryny. Limit Google to 50 000 URL-i na plik mapy witryny.

robots.ts

Ten sam wzorzec. Stwórz plik app/robots.ts:

// app/robots.ts
import { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/api/', '/dashboard/', '/admin/'],
      },
    ],
    sitemap: 'https://example.com/sitemap.xml',
  }
}

Dynamiczne obrazy OG

Next.js potrafi generować obrazy Open Graph w locie za pomocą next/og (oparty na bibliotece Satori od Vercel). To przydatne — zamiast ręcznie tworzyć obrazy OG do każdego posta, definiujesz szablon, a on renderuje się przy każdym żądaniu:

// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'

export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'

export default async function Image({
  params,
}: {
  params: { slug: string }
}) {
  const post = await getPost(params.slug)

  return new ImageResponse(
    (
      <div style={{
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center',
        padding: '60px',
        background: 'white',
        width: '100%',
        height: '100%',
      }}>
        <h1 style={{ fontSize: 48, fontWeight: 700 }}>
          {post.title}
        </h1>
        <p style={{ fontSize: 24, color: '#666' }}>
          {post.excerpt}
        </p>
      </div>
    ),
    { ...size }
  )
}

Next.js automatycznie ustawia meta tag og:image wskazujący na ten wygenerowany obraz. Bez ręcznego podpinania.

Optymalizacja obrazów z next/image

Komponent next/image obsługuje lazy loading, automatyczną konwersję do WebP/AVIF, responsywne rozmiary i zapobiega przesunięciom layoutu (CLS). Wszystko to bezpośrednio wpływa na Core Web Vitals, które Google wykorzystuje jako sygnał rankingowy.

import Image from 'next/image'

// To automatycznie:
// - Generuje warianty WebP/AVIF
// - Leniwie ładuje obrazy poniżej folda
// - Ustawia width/height, żeby zapobiec CLS
// - Serwuje responsywne rozmiary
<Image
  src="/hero.jpg"
  alt="Zrzut ekranu produktu pokazujący dashboard"
  width={1200}
  height={630}
  priority  // Above-the-fold: wyłącz lazy loading
/>

Dwie rzeczy, które deweloperzy konsekwentnie robią źle: zapominają o priority na obrazie LCP (Twoim największym obrazie above-the-fold) i używają surowych tagów <img> zamiast next/image. Oba pogorszają Core Web Vitals.

Dane strukturalne w Next.js

Dodawaj dane strukturalne JSON-LD bezpośrednio w Server Components. Żadna zewnętrzna paczka nie jest potrzebna:

// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }) {
  const post = await getPost(params.slug)

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: post.title,
    description: post.excerpt,
    datePublished: post.publishedAt,
    dateModified: post.updatedAt,
    author: {
      '@type': 'Person',
      name: post.author.name,
    },
    image: post.ogImage,
  }

  return (
    <>
      <script
        type="application/ld+json"
        // W Server Components Next.js to jest bezpieczne —
        // JSON jest generowany po stronie serwera z Twojej własnej bazy danych
        {...{ children: JSON.stringify(jsonLd) }}
      />
      <article>
        <h1>{post.title}</h1>
        <p>{post.content}</p>
      </article>
    </>
  )
}

Najczęstsze błędy SEO w Next.js

Widzę je na niemal każdej witrynie Next.js, którą audytuję. Każdy z nich kosztuje pozycje w rankingu.

  • Pobieranie danych w komponentach 'use client' — Twoja treść ładuje się dopiero po uruchomieniu JavaScriptu. Googlebot widzi pusty spinner ładowania. Przenieś pobieranie danych do Server Components.
  • Brak kanonicznych URL-i — Next.js domyślnie nie ustawia canonicali. Jeśli /blog/moj-post i /blog/moj-post?ref=twitter są indeksowane osobno, dzielisz autorytet strony. Ustaw alternates.canonical w metadanych.
  • Brak metadanych na dynamicznych ścieżkach — Statyczne strony mają eksport metadata. Dynamiczne strony potrzebują generateMetadata. Widziałem witryny z doskonałymi metadanymi na stronie głównej i <title>undefined</title> na każdej stronie produktu.
  • Używanie loading.tsx dla kluczowej treści — Plik loading wyświetla szkielet strony podczas ładowania treści. Googlebot może zaindeksować szkielet zamiast właściwej treści. Używaj loading.tsx dla drugorzędnych elementów UI, nie dla głównej treści strony.
  • Przekierowania po stronie klienta — Używanie router.push() do przekierowań. Wyszukiwarki nie wykonują przekierowań JavaScript niezawodnie. Użyj przekierowań w next.config.js lub middleware dla serwerowych 301/302.

SEO w React SPA: w zasadzie „nie rób tego”

Irytuje mnie, że muszę pisać tę sekcję. Jest 2026 rok. (Jeśli wciąż używasz Create React App do publicznej witryny w 2026, musimy porozmawiać.) Ale e-maile wciąż przychodzą, więc lecimy.

Nie używaj czystego React SPA do stron, które chcesz indeksować. Tyle. Czyste React SPA — Create React App, Vite z Reactem, cokolwiek — wysyła pusty szkielet HTML do każdego crawlera, który odwiedzi witrynę. Google może go w końcu wyrenderować. Crawlery AI nigdy tego nie zrobią. Używaj SPA do dashboardów, paneli administracyjnych, czegokolwiek za loginem. Nie używaj do czegokolwiek, co chcesz wyświetlać w wynikach wyszukiwania.

Tymczasowe rozwiązanie: React Helmet + Prerendering

Jeśli utknąłeś z React SPA i nie możesz migrować do Next.js (rozumiem — to się zdarza), oto podejście na taśmę klejącą:

// Użycie react-helmet-async do meta tagów
import { Helmet } from 'react-helmet-async'

function ProductPage({ product }) {
  return (
    <>
      <Helmet>
        <title>{product.name} | Your Store</title>
        <meta name="description" content={product.description} />
        <link rel="canonical"
          href={`https://example.com/products/${product.slug}`} />
      </Helmet>
      <h1>{product.name}</h1>
    </>
  )
}

React Helmet zarządza Twoimi tagami <head>. Ale — i to jest kluczowe — wciąż działa po stronie klienta. Googlebot nadal musi uruchomić JavaScript, żeby zobaczyć te tagi. Potrzebujesz usługi prerenderingu (Prerender.io, Rendertron lub własnego setupu z Puppeteerem), żeby serwować wyrenderowany HTML crawlerom.

To działa. Widziałem, że działa. Ale jest kruche, dodaje opóźnienie dla żądań crawlerów, kosztuje pieniądze i walczysz z frameworkiem zamiast z nim współpracować.

Jeśli Twoja aplikacja SPA ma ponad 50 stron wymagających indeksowania — a te strony mają dynamiczną treść, która zmienia się co tydzień, co oznacza, że cache prerenderingu wymaga ciągłej inwalidacji, co oznacza, że ktoś z Twojego zespołu utrzymuje teraz infrastrukturę crawlerów zamiast budować funkcjonalności produktu — koszt utrzymania prerenderingu przewyższa koszt migracji do Next.js. Liczyłem to dla klientów.

Jak udokumentowali Addy Osmani i Jason Miller na web.dev, prerendering zazwyczaj dodaje zauważalne opóźnienie po stronie serwera na stronę dla żądań crawlerów, a przypadki brzegowe z dynamiczną treścią czy stanami uwierzytelnienia często produkują nieaktualne lub błędne snapshoty. To ważna strategia przejściowa, ale nie rozwiązanie na stałe (web.dev: Rendering on the Web).

SEO w Nuxt: odpowiednik dla Vue

Jeśli Twój zespół jest w ekosystemie Vue, Nuxt jest dla Vue tym, czym Next.js dla Reacta. Ta sama idea: weź framework kliencki, dodaj renderowanie serwerowe, zarządzanie metadanymi i routing oparty na plikach. (Powinienem przyznać: spędziłem z Nuxtem znacznie mniej czasu niż z Next.js, więc traktuj moje opinie z dodatkową rezerwą.) Historia SEO jest mocna — nie tak dopracowana jak w Next.js w kilku obszarach, ale na tyle bliska, że nie powinna decydować o wyborze między tymi dwoma ekosystemami.

SSR domyślnie

Nuxt używa uniwersalnego renderowania od razu po instalacji. Każda strona jest renderowana na serwerze przy pierwszym wczytaniu, a potem hydratowana do nawigacji po stronie klienta. Nie musisz tego włączać. Musisz to wyłączyć. To właściwy domyślny tryb dla SEO.

useHead() i useSeoMeta()

System composable Nuxta do zarządzania metadanymi jest czysty. Dwie opcje w zależności od potrzebnego poziomu kontroli:

<!-- pages/blog/[slug].vue -->
<script setup>
const route = useRoute()
const { data: post } = await useFetch(`/api/posts/${route.params.slug}`)

// Opcja 1: useSeoMeta — typowane, pokrywa typowe przypadki
useSeoMeta({
  title: () => post.value?.title,
  description: () => post.value?.excerpt,
  ogTitle: () => post.value?.title,
  ogDescription: () => post.value?.excerpt,
  ogType: 'article',
  ogImage: () => post.value?.ogImage,
  twitterCard: 'summary_large_image',
})

// Opcja 2: useHead — pełna kontrola nad tagami <head>
useHead({
  link: [
    {
      rel: 'canonical',
      href: `https://example.com/blog/${route.params.slug}`,
    }
  ],
  script: [
    {
      type: 'application/ld+json',
      innerHTML: JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'Article',
        headline: post.value?.title,
        datePublished: post.value?.publishedAt,
      }),
    },
  ],
})
</script>

<template>
  <article>
    <h1>{{ post?.title }}</h1>
    <p>{{ post?.content }}</p>
  </article>
</template>

Bardzo lubię useSeoMeta(). Jest bardziej opiniowane niż Metadata API w Next.js, ale pokrywa 90% potrzeb z mniejszą ilością boilerplate'u. Bezpieczeństwo typów oznacza, że IDE wyłapie literówki w nazwach meta tagów — coś, co powodowało realne bugi na projektach, nad którymi pracowałem.

Renderowanie hybrydowe z Route Rules

routeRules w Nuxt pozwala mieszać strategie renderowania na poziomie ścieżek. To jest obszar, w którym Nuxt jest prawdopodobnie krok przed Next.js pod względem doświadczenia dewelopera:

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/':              { prerender: true },     // SSG — strona główna
    '/blog/**':       { isr: 3600 },           // ISR — rewalidacja co godzinę
    '/products/**':   { ssr: true },           // SSR — zawsze świeże
    '/dashboard/**':  { ssr: false },          // CSR — strony za loginem
    '/docs/**':       { prerender: true },     // SSG — dokumentacja
  }
})

Jeden obiekt konfiguracyjny, pięć różnych strategii renderowania. W Next.js ustawiasz export const dynamic = 'force-static' lub export const revalidate = 3600 w każdym pliku strony osobno. (W sumie to nie jest do końca sprawiedliwe — middleware Next.js też potrafi obsłużyć część tego centralnie. Ale podejście Nuxta jest bardziej jawne.) (To mi się kojarzy z debugowaniem warstwy GraphQL w Gatsby o 2 w nocy — niektóre frameworki optymalizują wygodę dewelopera kosztem jego zdrowia psychicznego.) Podejście Nuxta skaluje się lepiej, gdy masz wyraźne wzorce na poziomie ścieżek.

Moduł mapy witryny

Nuxt nie ma wbudowanego generowania mapy witryny jak Next.js. Potrzebujesz modułu @nuxtjs/sitemap (który jest oficjalnie utrzymywany i dobrze wspierany):

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/sitemap'],

  sitemap: {
    sources: ['/api/__sitemap__/urls'],
    exclude: ['/dashboard/**', '/admin/**'],
  },

  site: {
    url: 'https://example.com',
  },
})

Moduł automatycznie odkrywa Twoje statyczne strony na podstawie routingu opartego na plikach. Dla dynamicznych stron (posty blogowe, produkty) dostarczasz endpoint API zwracający listę URL-i. Pliki indeksu mapy witryny są generowane automatycznie po przekroczeniu 50 000 URL-i.

Nuxt Content do blogów i dokumentacji

Jeśli budujesz bloga lub stronę dokumentacji z Nuxtem, warto poznać moduł @nuxt/content. Czyta pliki Markdown/MDX z katalogu content/, generuje strony z pełnym wsparciem SSG i zawiera wbudowane wyszukiwanie. Z punktu widzenia SEO kluczowa korzyść jest taka, że każda strona treści jest prerenderowana jako statyczny HTML — najszybsza możliwa forma dostarczania treści wyszukiwarkom.

Historia z życia: migracja, która zmieniła moje podejście

Wynik audytu SEO w Google Lighthouse pokazujący zaliczone i niezaliczone testy SEO
Audyt SEO w Lighthouse ocenia stronę pod kątem meta tagów, crawlowalności i responsywności mobilnej. Źródło: Semrush Blog

Rozwiązanie: Renderuj kluczową treść po stronie serwera. Jeśli musisz mieć stany ładowania, upewnij się, że pojawiają się tylko dla drugorzędnych elementów UI. Weryfikacja: wyłącz JavaScript w Chrome DevTools (Ustawienia > Debugger > Wyłącz JavaScript), odśwież stronę. To, co widzisz, jest tym, co widzi większość crawlerów. Jeśli widzisz spinner — Google też go widzi.

4. Brakujące dane strukturalne

Dane strukturalne JSON-LD pomagają wyszukiwarkom zrozumieć, o czym jest Twoja strona. Strony produktowe bez schematu Product. Posty blogowe bez schematu Article. Strony FAQ bez schematu FAQPage. Każdy brakujący typ schematu to stracona szansa na wyniki rozszerzone.

Rozwiązanie: Dodaj JSON-LD do każdego typu strony. Weryfikacja: uruchom Google Rich Results Test na każdym typie strony. Zarówno Next.js, jak i Nuxt ułatwiają to — zobacz przykłady kodu wyżej. Albo pozwól, żeby SEOJuice zajął się tym automatycznie.

5. Przekierowania po stronie klienta

Używanie JavaScriptu (window.location.href, router.push(), navigateTo()) do przekierowań zamiast serwerowych 301/302. Wyszukiwarki nie śledzą niezawodnie przekierowań JavaScript. Link equity się nie przenosi. Strony, które powinny być skonsolidowane, pozostają rozdzielone.

Rozwiązanie: Używaj serwerowych przekierowań. W Next.js skonfiguruj je w next.config.js lub użyj middleware. W Nuxt użyj przekierowań w routeRules. Weryfikacja: curl -I https://twoja-witryna.com/stary-url — powinieneś zobaczyć kod statusu 301 lub 302 z nagłówkiem Location. Jeśli widzisz 200 OK, przekierowanie działa tylko po stronie klienta.

6. Zapominanie o crawlerach AI

To jest nowy problem. Nawet jeśli Twój setup SSR jest idealny dla Googlebota, sprawdź, czy Twój robots.txt nie blokuje crawlerów AI. Niektórzy dostawcy CDN (szczególnie Cloudflare z przełącznikiem „AI Bot”) domyślnie blokują GPTBot i podobne crawlery. Jeśli chcesz mieć widoczność w wyszukiwarkach AI, upewnij się, że mają dostęp do Twojej treści. Weryfikacja: curl https://twoja-witryna.com/robots.txt i szukaj GPTBot, ClaudeBot, PerplexityBot. Jeśli są zablokowane, a nie Ty to zrobiłeś — Twój CDN to zrobił.

I jeszcze jeden test, który pokrywa wszystko powyżej: kliknij prawym przyciskiem na dowolnej stronie, Pokaż źródło strony. Jeśli Twoja treść jest w surowym HTML, renderowanie serwerowe działa. Jeśli widzisz pusty <div id="root"></div>, nie działa. Uruchom też audyt SEO w Lighthouse — celuj w 100, to osiągalne z renderowaniem serwerowym. Jak napisali Jason Miller i Addy Osmani na web.dev: strategie renderowania wiążą się z realnymi kompromisami i nie możesz zakładać, że Twój JavaScript zostanie poprawnie wykonany przez każdego crawlera (web.dev: Rendering on the Web).

Uczciwa ocena

Narzędzie inspekcji URL w Google Search Console pokazujące status indeksacji
Inspekcja URL w Google Search Console pokazuje status indeksacji i sposób, w jaki Googlebot wyrenderował stronę. Źródło: Semrush Blog

Frameworki JavaScript i SEO mają skomplikowaną historię. Przez lata jedyną radą było „po prostu użyj renderowania po stronie serwera” i tyle. Teraz obraz jest bardziej zniuansowany, ale też bardziej rozwiązywalny.

Next.js z App Routerem to najlepsza opcja dla większości zespołów w 2026 roku. Server Components oznaczają, że Twoje strony są domyślnie renderowane na serwerze, Metadata API jest typowane i kompleksowe, a ekosystem jest dojrzały. Jeśli jesteś w ekosystemie Vue, Nuxt daje te same kluczowe korzyści z nieco innym API.

Czyste React SPA nie powinny być używane do publicznych stron. Mam dość widoku React SPA na witrynach marketingowych. Jest 2026 rok. Powinniśmy to już wiedzieć. I to się nie zmieni — jeśli cokolwiek, to rosnąca popularność crawlerów AI, które nie uruchamiają JavaScriptu, sprawia, że jest to trudniejsze, nie łatwiejsze.

Jedna rzecz, co do której naprawdę nie mam pewności: długoterminowa trajektoria Remix kontra Next.js pod kątem SEO. Filozofia Remix „tylko SSR, żadnej warstwy cachowania” jest atrakcyjna w swojej prostocie. Wraz z tanieniem edge computingu argument za ISR (kompromisem Next.js) słabnie. Nie wykluczam Remix w 2027 roku. Ale dziś Next.js ma więcej wbudowanych funkcji SEO, większy ekosystem i lepszą dokumentację. Używaj tego, co rozwiązuje Twój problem teraz.

Jeśli to wszystko brzmi jak dużo konfiguracji do ogarnięcia — bo tak jest. Właśnie dlatego istnieje SEOJuice: Ty wybierasz strategię renderowania, my automatyzujemy resztę.


FAQ

Czy Google faktycznie renderuje teraz JavaScript?

Tak, ale z zastrzeżeniami. Google używa Web Rendering Service (evergreen Chromium) do uruchamiania JavaScriptu. Renderowanie odbywa się w drugim przebiegu — po początkowym crawlowaniu — i może trwać od sekund do dni. Dla ugruntowanych witryn z dużym crawl budgetem jest zazwyczaj szybkie. Dla nowych lub niskoautorytetowych witryn opóźnienie może być znaczące. I co kluczowe: renderowanie przez Google nie jest gwarantowane. Strony, które zależą od złożonych interakcji JavaScript, wymagają uwierzytelnienia lub mają błędy renderowania, mogą nigdy nie zostać w pełni wyrenderowane. Bezpieczniejsze podejście to zawsze serwowanie kompletnego HTML w początkowej odpowiedzi.

Nuxt jest lepszy czy gorszy od Next.js pod kątem SEO?

Wybieraj na podstawie tego, czy Twój zespół pisze w Vue czy React, a nie na podstawie różnic w SEO.

Jak naprawić SEO w istniejącej React SPA bez przepisywania wszystkiego?

Najszybsza droga: dodaj Prerender.io lub podobną usługę prerenderingu jako middleware. Przechwytuje żądania crawlerów i serwuje wyrenderowany HTML. Konfiguracja zajmuje około godziny i natychmiast sprawia, że Twoja treść jest widoczna dla wyszukiwarek. Następnie zaplanuj stopniową migrację do Next.js — App Router może współistnieć z istniejącymi stronami, więc możesz migrować ścieżkę po ścieżce zamiast robić wielki rewolucyjny rewrite. Zacznij od stron z największym ruchem.

SEOJuice
Stay visible everywhere
Get discovered across Google and AI platforms with research-based optimizations.
Works with any CMS
Automated Internal Links
On-Page SEO Optimizations
Get Started Free

no credit card required

More articles

No related articles found.