Powrót do strony głównej

SSR dla SPA: konfiguracja indeksowania

Artykuł omawia konfigurację Server-Side Rendering do indeksowania aplikacji SPA przez wyszukiwarki. Omówiono Next.js, Nuxt.js, Angular Universal z przykładami kodu. Poprawa Core Web Vitals i organicznego ruchu.

SSR do indeksowania SPA: React, Vue, Angular
Advertisement 728x90

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 |

|----------|-----|-----|--------------|-----|

Google AdInline article slot

| 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 |

Google AdInline article slot

| 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.

Google AdInline article slot

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

Advertisement 728x90

Czytaj dalej