StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

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

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

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

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

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

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

RealizacjeO mnieBlog
Porozmawiajmy
PL
EN

Nowoczesne strony internetowe dla firm, które myślą odważnie.

Przewiń do góry

Nazwa

StriveLab Maciej Sala

NIP

6772218995

REGON

524008527

E-mail

contact@strivelab.pl

Usługi główne
  • Tworzenie stron internetowych
  • Strony internetowe Next.js
  • Strony internetowe Astro
  • Strony internetowe React
Inne usługi
  • Usługi
  • SEO & Performance Sprint
  • QA & Stabilizacja
  • Konsultacje Product / Delivery
  • Automatyzacja Procesów AI
  • Aplikacje webowe Next.js
  • Współpraca ciągła
Strony
  • O mnie
  • Usługi
  • Realizacje
  • Blog

© 2026 StriveLab.pl

Polityka prywatności
Next.jsSEO

Hreflang i canonical w Next.js — SEO wielojęzycznych stron bez duplikacji

Jak poprawnie ustawić hreflang i canonical w Next.js App Router? Unikanie duplikacji treści, konfiguracja metadata API, wielojęzyczna sitemap i typowe błędy SEO.

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
10 kwietnia 2026 14:50
Czytanie
4 min czytania
Aktualizacja
25 maja 2026 12:50

Hreflang i canonical rozwiązują dwa różne problemy wielojęzycznego SEO: właściwy język wyniku i właściwy adres do indeksowania. W Next.js App Router oba mechanizmy da się generować spójnie przez Metadata API, bez ręcznego utrzymywania tagów w każdej podstronie.

Artykuł w skrócie

  • Hreflang musi być wzajemny — każda para językowa musi wskazywać na siebie nawzajem; x-default + każda wersja językowa, inaczej Google ignoruje cały zestaw
  • Metadata API w App Router — generuj alternates.languages w generateMetadata() na poziomie layoutu lub page; nie wklejaj ręcznie do <head>
  • Canonical eliminuje duplikaty — parametry URL, trailing slash i protokół http/https tworzą osobne wersje strony; canonical wskazuje kanoniczną
  • Sitemap z alternates — wielojęzyczna sitemap powinna zawierać <xhtml:link rel="alternate"> dla każdej wersji językowej każdego URL
  • Weryfikacja w GSC — sprawdzaj raport Internacjonalizacja w Search Console; błędy hreflang to jedne z najczęstszych problemów technicznego SEO

Czym jest hreflang i dlaczego jest krytyczny?

Hreflang to atrybut wskazujący wyszukiwarce językowe i regionalne warianty tej samej strony, np. polski i angielski. Poprawnie ustawiony zapobiega duplikacji i kieruje użytkownika do właściwej wersji językowej. to atrybut HTML, który informuje Google, która wersja językowa strony jest przeznaczona dla danego regionu lub języka. Bez hreflang Google może wyświetlić polską wersję użytkownikowi anglojęzycznemu lub potraktować wersje językowe jako duplikaty (kanibalizacja). Może też indeksować tylko jedną wersję. Krótko mówiąc: bez hreflang czeka nas bałagan, podczas gdy hreflang wprowadza kontrolę i porządek.

Canonical wskazuje preferowany URL, który ma być traktowany jako główna wersja podobnej lub zduplikowanej strony. (link rel="canonical") wskazuje „źródłową" wersję strony i w ten sposób eliminuje duplikaty wynikające z parametrów URL, trailing slashy, czy wariantów http/https. Oba tagi "współpracują" ze sobą, ponieważ hreflang łączy wersje językowe, canonical wyznacza kanoniczną wersję w każdym języku.

Uwaga

Najczęstszy błąd to canonical wskazujący inną wersję językową niż aktualna strona. Każdy język powinien mieć własny canonical do samego siebie, a wersje między sobą łączysz przez hreflang.

Hreflang w Next.js App Router — Metadata API

Code
// app/[locale]/page.tsx
import type { Metadata } from 'next'
 
const locales = ['pl', 'en', 'de']
const baseUrl = 'https://strivelab.pl'
 
// Polska wersja na rootu domeny, pozostałe języki z prefiksem
const getLocaleUrl = (locale: string, path = '') =>
  locale === 'pl' ? `${baseUrl}${path}` : `${baseUrl}/${locale}${path}`
 
export async function generateMetadata({
  params,
}: {
  params: Promise<{ locale: string }>
}): Promise<Metadata> {
  const { locale } = await params
 
  return {
    alternates: {
      canonical: getLocaleUrl(locale),
      languages: {
        pl: baseUrl,
        en: `${baseUrl}/en`,
        de: `${baseUrl}/de`,
        'x-default': baseUrl,
      },
    },
  }
}

Wynikowy HTML:

Code
<link rel="canonical" href="https://strivelab.pl/" />
<link rel="alternate" hreflang="pl" href="https://strivelab.pl/" />
<link rel="alternate" hreflang="en" href="https://strivelab.pl/en" />
<link rel="alternate" hreflang="de" href="https://strivelab.pl/de" />
<link rel="alternate" hreflang="x-default" href="https://strivelab.pl/" />

Polska wersja działa na rootu domeny (https://strivelab.pl/) bez prefiksu /pl — domena .pl już precyzuje rynek. Pozostałe języki mają własny prefiks (/en, /de). Kluczową zasadą jest by canonical, hreflang, Sitemap to plik z listą ważnych URL-i, który pomaga wyszukiwarkom szybciej odkrywać i aktualizować podstrony. i linkowanie wewnętrzne były konsekwentnie używane wobec tego samego wariantu URL.

Diagram
Poprawna relacja canonical i hreflang dla wersji językowych

Dynamiczne strony z parametrami

Code
// app/[locale]/blog/[slug]/page.tsx
export async function generateMetadata({
  params,
}: {
  params: Promise<{ locale: string; slug: string }>
}): Promise<Metadata> {
  const { locale, slug } = await params
  const post = await getPost(slug, locale)
 
  return {
    title: post.title,
    description: post.excerpt,
    alternates: {
      canonical: getLocaleUrl(locale, `/blog/${slug}`),
      languages: Object.fromEntries(
        locales.map((l) => [
          l,
          getLocaleUrl(l, `/blog/${post.slugs[l] || slug}`),
        ]),
      ),
    },
  }
}

Jeśli slugi są tłumaczone (/pl/blog/jak-zbudowac-strone vs /en/blog/how-to-build-website), każda wersja językowa musi wskazywać na poprawny slug w danym języku.

Canonical — eliminacja duplikacji

Trailing slash

Code
// next.config.ts
const nextConfig = {
  trailingSlash: false, // Wymuś brak trailing slash
}

Dodatkowo w pliku middleware.ts:

Code
if (pathname.length > 1 && pathname.endsWith('/')) {
  return NextResponse.redirect(new URL(pathname.slice(0, -1), request.url), 308)
}

Parametry URL

Strona /products i /products?sort=price to ta sama treść. Canonical powinien wskazywać wersję bez parametrów:

Code
export const metadata: Metadata = {
  alternates: {
    canonical: '/products', // Bez ?sort=, ?page=, ?filter=
  },
}

www vs non-www

Wybierz jedną wersję i przekieruj drugą. W Vercel — konfiguracja domeny. Na VPS — nginx redirect:

Code
server {
    server_name www.strivelab.pl;
    return 301 https://strivelab.pl$request_uri;
}

Helper do generowania alternates

Code
// lib/seo.ts
const baseUrl = 'https://strivelab.pl'
const locales = ['pl', 'en'] as const
 
const getLocaleUrl = (locale: (typeof locales)[number], path: string) =>
  locale === 'pl' ? `${baseUrl}${path}` : `${baseUrl}/${locale}${path}`
 
export function generateAlternates(
  locale: (typeof locales)[number],
  localizedPaths: Record<(typeof locales)[number], string>,
) {
  const languages = Object.fromEntries(
    locales.map((l) => [l, getLocaleUrl(l, localizedPaths[l])]),
  )
 
  return {
    canonical: getLocaleUrl(locale, localizedPaths[locale]),
    languages: {
      ...languages,
      'x-default': `${baseUrl}${localizedPaths.pl}`,
    },
  }
}
Code
// Użycie
export async function generateMetadata(): Promise<Metadata> {
  return {
    alternates: generateAlternates('pl', {
      pl: '/uslugi',
      en: '/services',
    }),
  }
}

Sitemap wielojęzyczna z alternates

Code
// app/sitemap.ts
import type { MetadataRoute } from 'next'
 
export default function sitemap(): MetadataRoute.Sitemap {
  const baseUrl = 'https://strivelab.pl'
  const locales = ['pl', 'en'] as const
 
  const getLocaleUrl = (locale: string, path: string) =>
    locale === 'pl' ? `${baseUrl}${path}` : `${baseUrl}/${locale}${path}`
 
  const pages = [
    { plPath: '', enPath: '' },
    { plPath: '/uslugi', enPath: '/services' },
    { plPath: '/kontakt', enPath: '/contact' },
    { plPath: '/blog', enPath: '/blog' },
  ]
 
  return pages.flatMap((page) =>
    locales.map((locale) => ({
      url: getLocaleUrl(locale, locale === 'pl' ? page.plPath : page.enPath),
      lastModified: new Date(),
      alternates: {
        languages: Object.fromEntries(
          locales.map((l) => [
            l,
            getLocaleUrl(l, l === 'pl' ? page.plPath : page.enPath),
          ]),
        ),
      },
    })),
  )
}

Typowe błędy hreflang

1. Brakujący x-default

x-default wskazuje wersję dla użytkowników, których język nie jest obsługiwany. Warto zaznaczyć, że nie jest to obowiązkowe w każdym projekcie, ale zwykle warto go dodać, jeśli masz stronę domyślną albo selektor języka.

2. Brak wzajemności (reciprocal links)

Jeśli strona PL wskazuje na EN, to EN musi wskazywać z powrotem na PL. W wypadku braku wzajemności = Google ignoruje hreflang.

3. Canonical i hreflang wskazują na różne URL-e

Canonical powinien wskazywać na siebie (w danym języku), nie na inną wersję językową. Każda wersja językowa ma własny canonical.

4. Hreflang na stronach z noindex

Jeśli strona ma atrybut noindex, wtedy hreflang jest ignorowany.

Weryfikacja

  • Google Search Console → inspekcja konkretnych URL-i i raport indeksowania,
  • Ahrefs / Screaming Frog — audyt hreflang na dużą skalę,
  • Ręczne sprawdzenie — View Source → szukaj rel="alternate" hreflang.

Werdykt Labu

Hreflang i canonical w Next.js App Router to konfiguracja Metadata API i nie wymaga ręcznego pisania tagów <link>. Kluczowych zasad jest kilka: każda wersja językowa ma własny canonical, linki hreflang muszą być wzajemne, a x-default jest zwykle pomocny, choć nie jest wymogiem dla każdej strony. Najczęstszy błąd to ustawienie canonical wszystkich wersji na język domyślny — to wyrzuca tłumaczenia z indeksu rynków docelowych.

Jeśli budujesz wielojęzyczną stronę w Next.js i chcesz mieć pewność, że hreflang oraz canonical nie rozbiją Ci indeksacji na warianty tej samej strony, przeprowadzę audyt i wdrożę poprawną konfigurację albo zobacz, jak realizuję projekty w Next.js.

  • Czym jest hreflang i dlaczego jest krytyczny?1 min
  • Hreflang w Next.js App Router — Metadata API1 min
  • Canonical — eliminacja duplikacji1 min
  • Helper do generowania alternates1 min
  • Sitemap wielojęzyczna z alternates1 min
  • Typowe błędy hreflang1 min
  • Weryfikacja1 min
  • Werdykt Labu1 min

Często zadawane pytania

ŹródłaZweryfikowano: 31 maja 2026

Dokumentacja Next.js i Google dotycząca wielojęzyczności zweryfikowana podczas redakcji artykułu.

  • Next.js — Metadata: alternates
  • Google — Localized versions of your pages
  • Google — Consolidate duplicate URLs (canonical)
  • Next.js — Internationalization

Seria

Next.js i SEO techniczne
Część 3 / 5
  1. 1Next.js a SEO — kiedy naprawdę daje przewagę nad zwykłym Reactem
  2. 2Next.js Sitemap i robots.txt — automatyczna generacja z App Routera
  3. Hreflang i canonical w Next.js — SEO wielojęzycznych stron bez duplikacji
  4. 4Google Search Console + Next.js — indeksacja, błędy, performance i co z nimi robić
  5. 5Migracja z WordPress do Next.js — redirecty 301 i pozycje SEO
Maciej Sala

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 rozwija własne projekty side-projecty.

Moje artykułyWięcej o mnie

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

Skontaktuj się ze mną

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Next.js Sitemap i robots.txt — automatyczna generacja z App Routera
Next.js Sitemap i robots.txt — automatyczna generacja z App Routera

Jak generować sitemap.xml i robots.txt w Next.js App Router? Natywne API konwencji plików vs next-sitemap — dynamiczne sitemaps, lastmod, changefreq i priorytety.

Maciej Sala

Maciej Sala

Founder Strivelab

10 kwietnia 2026
Google Search Console + Next.js — indeksacja, błędy, performance i co z nimi robić
Google Search Console + Next.js — indeksacja, błędy, performance i co z nimi robić

Jak korzystać z Google Search Console dla strony Next.js? Weryfikacja, sitemap, indeksacja, Core Web Vitals, crawl budget i najczęstsze problemy — praktyczny poradnik.

Maciej Sala

Maciej Sala

Founder Strivelab

11 kwietnia 2026
Next.js a SEO — kiedy naprawdę daje przewagę nad zwykłym Reactem
Next.js a SEO — kiedy naprawdę daje przewagę nad zwykłym Reactem

Jak Next.js wpływa na SEO w praktyce? SSR, SSG, metadata, Core Web Vitals i techniczne ograniczenia, o których warto wiedzieć przed wyborem frameworka.

Maciej Sala

Maciej Sala

Founder Strivelab

15 lipca 2025
Poprzedni wpisPagespeed 100/100 w Next.js — case study optymalizacji strony usługowejJak osiągnąć wynik 100/100 w PageSpeed Insights dla strony Next.js? Case study optymalizacji LCP, INP i CLS — fonty, obrazy, JavaScript, third-party scripts i Server Components.
Maciej Sala

Maciej Sala

Founder Strivelab

10 kwietnia 2026
Następny wpisVercel vs Coolify vs VPS — gdzie hostować Next.js w 2026?Porównanie hostingu Next.js: Vercel, Coolify i własny VPS. Koszty, cache i ISR, wymagania RODO, Docker oraz kryteria decyzji dla produkcji.
Maciej Sala

Maciej Sala

Founder Strivelab

10 kwietnia 2026