Server-Side Rendering dla aplikacji jednostronicowych: jak zapewnić indeksację w wyszukiwarkach
Roboty indeksujące, takie jak Googlebot czy YandexBot, często widzą pusty kod HTML w aplikacjach jednostronicowych (SPA) opartych na CSR. Bez Server-Side Rendering nie czekają na wykonanie kodu JavaScript i opuszczają stronę — co skutkuje jej brakiem w indeksie. SSR rozwiązuje ten problem, generując kompletny kod HTML na serwerze jeszcze przed wysłaniem go do przeglądarki. Dzięki temu skracane są czasy FCP, LCP i TTFB, poprawiane są Core Web Vitals oraz zwiększa się widoczność w wynikach wyszukiwania.
Porównanie metod renderowania
| Parametr | CSR | SSR | Prerendering | SSG |
|----------|-----|-----|--------------|-----|
| Miejsce renderowania | Przeglądarka | Serwer (dla każdego żądania) | Z góry, dla wybranych adresów URL | Z góry, dla wszystkich stron |
| FCP/LCP | Powolne | Szybkie | Szybkie | Bardzo szybkie |
| SEO | Słabe | Doskonałe | Dobre | Doskonałe |
| Aktualność danych | W czasie rzeczywistym | W czasie rzeczywistym | Przestarzałe | Przestarzałe |
| Obciążenie serwera | Minimalne | Wysokie | Niskie | Minimalne |
SSR najlepiej sprawdza się na dynamicznych stronach: katalogach produktów, aktualnościach. Prerendering warto stosować dla rzadko zmieniających się adresów URL. SSG jest optymalnym wyborem dla treści statycznych.
React + Next.js: krok po kroku
Utwórz projekt:
npx create-next-app@latest moja-aplikacja-ssr
cd moja-aplikacja-ssr
npm run dev
Dla dynamicznych stron użyj getServerSideProps:
// pages/product/[id].jsx
export async function getServerSideProps(context) {
const { id } = context.params;
const res = await fetch(`https://api.przyklad.pl/produkty/${id}`);
const produkt = await res.json();
if (!res.ok) {
return { notFound: true };
}
return {
props: { produkt },
};
}
export default function StronaProduktu({ produkt }) {
return (
<main>
<h1>{produkt.nazwa}</h1>
<p>{produkt.opis}</p>
</main>
);
}
Dodaj metatagów:
import Head from 'next/head';
export default function StronaProduktu({ produkt }) {
return (
<>
<Head>
<title>{produkt.nazwa} | Sklep internetowy</title>
<meta name="description" content={produkt.krotkiOpis} />
<meta property="og:title" content={produkt.nazwa} />
<meta property="og:image" content={produkt.adresObrazka} />
</Head>
<main>
<h1>{produkt.nazwa}</h1>
</main>
</>
);
}
Wdrożenie: Vercel, Netlify lub Node.js z PM2.
Bezbłędna hydratacja
Hydratacja to proces, w którym klientowy React „ożywia” HTML wygenerowany po stronie serwera. Unikaj błędów typu mismatch:
- Synchronizuj dane pomiędzy serwerem a klientem.
- Sprawdzaj środowisko przed użyciem
window/document. - Monitoruj konsolę pod kątem ostrzeżeń.
const czyJestPrzegladarka = typeof window !== 'undefined';
Vue + Nuxt.js: renderowanie po stronie serwera
Utwórz projekt:
npx nuxi@latest init moja-aplikacja-nuxt
cd moja-aplikacja-nuxt
npm install
npm run dev
Dane serwerowe:
<!-- pages/product/[id].vue -->
<script setup>
const trasa = useRoute();
const { data: produkt, error } = await useFetch(
`/api/produkty/${trasa.params.id}`
);
if (error.value) {
throw createError({ statusCode: 404, message: 'Nie znaleziono' });
}
useHead({
title: produkt.value?.nazwa,
meta: [
{ name: 'description', content: produkt.value?.krotkiOpis },
],
});
</script>
<template>
<main>
<h1>{{ produkt?.nazwa }}</h1>
<p>{{ produkt?.opis }}</p>
</main>
</template>
Konfiguracja nuxt.config.ts:
export default defineNuxtConfig({
ssr: true,
app: {
head: {
htmlAttrs: { lang: 'pl' },
link: [{ rel: 'canonical', href: 'https://przyklad.pl' }],
},
},
nitro: {
preset: 'node-server',
},
});
Angular Universal: integracja SSR
Dla Angulara 17+:
ng add @angular/ssr
Endpoint serwerowy w server.ts:
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
// ... inne importy
const serwer = express();
// ...
serwer.get('*', (req, res, next) => {
commonEngine
.render({
bootstrap: AppServerModule,
documentFilePath: join(browserDistFolder, 'index.html'),
url: req.originalUrl,
publicPath: browserDistFolder,
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
})
.then((html) => res.send(html))
.catch(next);
});
Budowa:
npm run build:ssr
npm run serve:ssr
Sprawdzaj platformę:
import { isPlatformBrowser } from '@angular/common';
import { inject, PLATFORM_ID } from '@angular/core';
const platformId = inject(PLATFORM_ID);
if (isPlatformBrowser(platformId)) {
console.log(window.location.href);
}
Integracja z Headless CMS
Dla Next.js + Contentful GraphQL:
export async function getServerSideProps({ params }) {
const zapytanie = `
query PobierzArtykul($slug: String!) {
articleCollection(where: { slug: $slug }) {
items {
title
body
seo {
title
description
}
}
}
}
`;
const res = await fetch(
`https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.CONTENTFUL_TOKEN}`,
},
body: JSON.stringify({ zapytanie, zmienne: { slug: params.slug } }),
}
);
const { dane } = await res.json();
// ...
}
Kluczowe wnioski
- SSR generuje gotowy kod HTML dla robotów indeksujących, eliminując problemy z indeksacją aplikacji CSR.
- Next.js, Nuxt.js i Angular Universal to sprawdzone rozwiązania do wdrożenia SSR.
- Błąd hydration mismatch powoduje ponowne renderowanie — zawsze sprawdzaj środowisko wykonania.
- Core Web Vitals (FCP, LCP, TTFB) poprawiają się o 50–80% po przejściu na SSR.
- Używaj pamięci podręcznej Redis, aby uzyskać TTFB poniżej 200 ms przy dużym obciążeniu.
— Editorial Team
Brak komentarzy.