Przejdź do treści

Migracja z WordPress do Next.js — redirecty 301 i pozycje SEO

Migracja z WordPress do Next.js bez utraty widoczności w Google — jak zaplanować redirecty 301, zanim wyłączysz stary serwis.

Maciej Sala

Founder StriveLab

6 min czytaniaOpublikowano 11 kwietnia 2026 (Aktualizacja 25 czerwca 2026)

Od czego zależy zachowanie SEO przy migracji WordPress do Next.js?

Migracja z WordPress na Next.js to dobra i bardzo przyszłościowa inwestycja. Strona będzie szybsza, zanotuje skok w , zmniejszy się ryzyko narażenia na ataki i będziemy mieli większą kontrolę nad kodem. O tym, czy zachowasz pozycje w Google, decyduje przemyślany, dobry plan . Z dobrym planem przejście jest niemal niewidoczne dla wyszukiwarki, a bez niego ryzykujesz to, na co pracowałeś latami.

Cały ciężar spoczywa na URL-ach, które Google zaindeksował, przypisał im autorytet i rankuje na konkretne frazy. Jeśli je zabezpieczysz, migracja jest niewidoczna dla algorytmu, ale jeśli je pominiesz, musisz liczyć się z konsekwencjami. Są cztery:

  • utracone pozycje — Google widzi nowe URL-e jako nowe strony, bez wieloletniej historii,
  • błędy 404 — stare adresy przestają działać,
  • utratę backlinków — linki zewnętrzne prowadzą donikąd,
  • spadek ruchu organicznego na tygodnie lub długie miesiące.

Krok 1: inwentaryzacja zaindeksowanych URL-i w WordPressie

Przed migracją zrób pełną, dokładną listę zaindeksowanych stron:

Code
# Pobierz wszystkie zaindeksowane URL-e z Google Search Console
# GSC → Performance → Pages → Eksportuj do CSV
 
# Lub użyj screaming frog / sitemap
curl -s https://twoj-wordpress.pl/sitemap_index.xml

Następnie stwórz arkusz z czterema kolumnami: stary URL, nowy URL, status (migruj//usuń) oraz priorytet (ruch organiczny).

Krok 2: eksport treści z WordPressa przez WP-CLI lub REST API

Code
# WP-CLI — eksport do JSON (najlepsze do programowej migracji)
wp post list --post_type=post --format=json --fields=ID,post_title,post_name,post_content,post_date,post_excerpt > posts.json
wp post list --post_type=page --format=json --fields=ID,post_title,post_name,post_content > pages.json
 
# Eksport mediów
wp media list --format=json --fields=ID,guid,post_title,alt_text > media.json

Alternatywą jest użycie WordPress :

Code
// scripts/export-wordpress.ts
async function exportPosts() {
  let page = 1
  let allPosts: any[] = []
 
  while (true) {
    const res = await fetch(
      `https://twoj-wordpress.pl/wp-json/wp/v2/posts?per_page=100&page=${page}&_embed`,
    )
 
    if (!res.ok) break
 
    const posts = await res.json()
    if (posts.length === 0) break
 
    allPosts = [...allPosts, ...posts]
    page++
  }
 
  // Zapisz do pliku
  const fs = require('fs')
  fs.writeFileSync('wordpress-posts.json', JSON.stringify(allPosts, null, 2))
  console.log(`Wyeksportowano ${allPosts.length} postów`)
}

Krok 3: konwersja treści WordPress na MDX w Next.js

Code
// scripts/convert-to-mdx.ts
import TurndownService from 'turndown'
import fs from 'fs'
import path from 'path'
 
const turndown = new TurndownService({
  headingStyle: 'atx',
  codeBlockStyle: 'fenced',
})
 
interface WPPost {
  title: { rendered: string }
  slug: string
  content: { rendered: string }
  excerpt: { rendered: string }
  date: string
  _embedded?: {
    'wp:featuredmedia'?: [{ source_url: string; alt_text: string }]
    'wp:term'?: [{ name: string }[]]
  }
}
 
function convertPost(post: WPPost): string {
  const tags = post._embedded?.['wp:term']?.[0]?.map((t) => t.name) || []
  const image = post._embedded?.['wp:featuredmedia']?.[0]?.source_url || ''
  const excerpt = post.excerpt.rendered.replace(/<[^>]*>/g, '').trim()
 
  // Konwersja HTML → Markdown
  const content = turndown.turndown(post.content.rendered)
 
  // Frontmatter MDX
  const frontmatter = `---
title: '${post.title.rendered.replace(/'/g, "\\'")}'
description: '${excerpt.slice(0, 160)}'
datePublished: '${post.date}'
author: 'Maciej Sala'
tags: [${tags.map((t) => `'${t}'`).join(', ')}]
---`
 
  return `${frontmatter}\n\n${content}`
}
 
// Konwertuj wszystkie posty
const posts: WPPost[] = JSON.parse(
  fs.readFileSync('wordpress-posts.json', 'utf-8'),
)
 
for (const post of posts) {
  const mdx = convertPost(post)
  const filePath = path.join('content/blog', `${post.slug}.mdx`)
  fs.writeFileSync(filePath, mdx)
}
 
console.log(`Skonwertowano ${posts.length} postów do MDX`)

Krok 4: mapowanie URL-i i przekierowania 301 w Next.js

Kluczowym zadaniem jest zapewnienie, że każdy stary URL ma swój odpowiednik w nowej witrynie, dlatego jeśli tylko jest taka możliwość — zachowaj strukturę URL. Jeśli zmiany nie da się uniknąć, musisz skonfigurować przekierowania 301 (permanent redirect) ze starych adresów na nowe.

Code
// Typowe mapowanie WordPress → Next.js
const urlMap: Record<string, string | null> = {
  // WordPress domyślne
  '/wp-admin': null, // Nie przekierowuj
  '/wp-login.php': null, // Nie przekierowuj
  '/feed': '/blog/rss.xml', // RSS
 
  // Struktury permalink
  // WordPress: /2024/01/moj-post/ → Next.js: /blog/moj-post
  // WordPress: /kategoria/nazwa/ → Next.js: /blog/tag/nazwa
}
Code
// next.config.ts
const nextConfig = {
  async redirects() {
    return [
      // Redirect WordPress permalink structure
      {
        source: '/:year(\\d{4})/:month(\\d{2})/:slug',
        destination: '/blog/:slug',
        permanent: true, // 301
      },
      // Redirect category pages
      {
        source: '/wp-category/:slug',
        destination: '/blog/tag/:slug',
        permanent: true,
      },
      // Redirect tag pages
      {
        source: '/tag/:slug',
        destination: '/blog/tag/:slug',
        permanent: true,
      },
      // Redirect feed
      {
        source: '/feed',
        destination: '/blog/rss.xml',
        permanent: true,
      },
      // WordPress artifacts
      {
        source: '/wp-content/:path*',
        destination: '/media/:path*', // Jeśli przeniosłeś media
        permanent: true,
      },
      // Konkretne strony z innym URL
      {
        source: '/o-nas',
        destination: '/o-mnie',
        permanent: true,
      },
    ]
  },
}

Redirecty 301 przy dużej skali jako dane

Reguły w next.config.ts świetnie obsługują wzorce, ale prawdziwy WordPress rzadko jest aż tak regularny i może się im wymykać. Zostają dziesiątki czy setki starych permalinków, które nie mieszczą się w żadnym wzorcu: ręcznie zmieniane slugi, archiwalne adresy, strony usunięte i scalone. Wpisywanie ich pojedynczo w configu szybko staje się nie do utrzymania.

Lepiej potraktować takie redirecty jako dane — jedną mapę, którą generujesz z arkusza mapowania URL-i i z której korzysta zarówno aplikacja, jak i testy.

Code
// src/seo/redirects.ts
export const redirects = [
  { source: '/2024/01/stary-slug/', destination: '/blog/nowy-slug/' },
  { source: '/uslugi/dawna-nazwa/', destination: '/uslugi/next-js/' },
  // ...wygenerowane z arkusza mapowania
] as const
Code
// middleware.ts
import { NextResponse, type NextRequest } from 'next/server'
import { redirects } from './src/seo/redirects'
 
const map = new Map(redirects.map((r) => [r.source, r.destination]))
 
export function middleware(request: NextRequest) {
  const target = map.get(request.nextUrl.pathname)
  if (target) {
    return NextResponse.redirect(new URL(target, request.url), 301)
  }
  return NextResponse.next()
}

Wzorce zostają w next.config.ts, a indywidualne adresy w mapie danych. Tę samą listę możesz przetestować automatycznie, zamiast klikać stare URL-e po deployu — opisuję to w artykule o QA technicznego SEO.

Krok 5: migracja obrazów z wp-content do next/image

Code
// scripts/download-images.ts
import fs from 'fs'
import path from 'path'
 
const media: { guid: string; post_title: string }[] = JSON.parse(
  fs.readFileSync('media.json', 'utf-8'),
)
 
async function downloadImage(url: string, filename: string) {
  const res = await fetch(url)
  const buffer = await res.arrayBuffer()
  const outputPath = path.join('public/images/blog', filename)
  fs.writeFileSync(outputPath, Buffer.from(buffer))
}
 
async function migrateImages() {
  for (const item of media) {
    const url = item.guid
    const filename = path.basename(new URL(url).pathname)
 
    try {
      await downloadImage(url, filename)
      console.log(`✓ ${filename}`)
    } catch {
      console.error(`✗ ${filename}`)
    }
  }
}

Po pobraniu kolejny krok to zaktualizowanie lokalnych linków do obrazków w :

Code
# Zamień stare URL-e mediów na lokalne ścieżki
sed -i 's|https://twoj-wordpress.pl/wp-content/uploads/|/images/blog/|g' content/blog/*.mdx

Krok 6: weryfikacja SEO przed przełączeniem domeny

Lista działań do odchaczenia przed przełączeniem:

  • Każdy stary URL posiada redirect 301 lub odpowiednik w nowej strukturze

  • Redirecty prowadzą prosto do celu, bez łańcuchów i pętli (A → C, nie A → B → C)

  • Sitemap zawiera wszystkie zmigrowane strony
  • Meta tagi (title, description) są poprawnie zachowane z WordPressa

  • Obrazy zostały przeniesione i są dostępne
  • Schema.org / zostały poprawnie zachowane lub odtworzone

  • Kanoniczne URL-e są poprawne
  • robots.txt nie blokuje ważnych ścieżek
  • Google Search Console zostało poprawnie zweryfikowane na nowej stronie

Krok 7: przełączenie domeny i monitoring w Google Search Console

  1. Pierwszym krokiem jest wdrożenie nowej strony Next.js,
  2. Następnie przekieruj domenę na nowy hosting,
  3. Sprawdź redirecty (curl -I stare-url → 301 → nowy-url),
  4. Zgłoś nową sitemap w Google Search Console,
  5. Użyj URL Inspection na kluczowych stronach,
  6. Monitoruj uważnie ruch w przez 4-6 tygodni.

Profesjonalna migracja nie musi wiązać się ze spadkiem ruchu. Ewentualne wahania w pierwszych 2 tygodniach to zwykle pochodna tempa, w jakim boty Google przetwarzają mapę przekierowań 301 i reindeksują nową strukturę. Celem jest wdrożenie całkowicie przezroczyste dla Google.

Najczęstsze błędy migracji WordPress do Next.js, które kosztują pozycje

Tutaj chcę zwrócić uwagę na kilka pułapek, które mogą pomóc zabezpieczyć migrację.

  1. Link kanoniczny wskazujący stary adres. Wtyczki SEO w WordPress wstrzykują linki kanoniczne w każdy wpis. Jeśli przy konwersji treści przeniesiesz je dosłownie, nowa strona może wskazywać linkiem kanonicznym na nieistniejący już URL WordPressa. Link kanoniczny generuj z nowej struktury, a nie kopiuj z eksportu.
  2. Zgubione metadane. Title i meta description z WordPressa (zwykle z Yoast albo RankMath) nie są częścią treści posta tylko siedzą w osobnych polach. Eksport samego post_content ich nie zabierze, dlatego zmapuj je osobno, inaczej nowe strony dostaną generyczne tytuły i stracą trafność na frazy.
  3. Dane strukturalne tylko z wtyczki. Schema (Article, BreadcrumbList, FAQ) na WordPressie zwykle dokłada wtyczka, więc w eksporcie HTML jej nie ma. Z kolei w Next.js trzeba ją odtworzyć ręcznie jako .
  4. Za wcześnie usunięta stara sitemapa. Zostaw wp-sitemap.xml (dostępną jeszcze przez jakiś czas po przełączeniu) i zgłoś ją obok nowej. W ten sposób pomożesz Google szybciej odwiedzić stare adresy i przetworzyć przekierowania.

Migracja z WordPressa do Next.js ma sens przy projektach z dużą interaktywnością i rozbudowanym backendem. Dla blogów i stron contentowych, gdzie celem jest głównie prędkość i niski koszt utrzymania, identyczny plan redirectów 301 działa równie dobrze przy przejściu na Astro — co opisuję w artykule o migracji bloga z WordPress na Astro.

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

Często zadawane pytania

Co jest najważniejsze przy migracji WordPressa do Next.js?

Najważniejsze jest pełne mapowanie starych URL-i, poprawne redirecty 301, zachowanie metadanych, migracja obrazów i kontrola indeksacji po wdrożeniu. Bez tego łatwo stracić ruch organiczny.

Czy można zmienić strukturę URL podczas migracji?

Można, ale każda zmiana powinna mieć jednoznaczny redirect 301 ze starego adresu na nowy. Przy większych serwisach warto przygotować mapę URL-i i przetestować ją przed deployem.

Kiedy sprawdzić efekty migracji w Google Search Console?

Pierwsze błędy warto sprawdzać od razu po wdrożeniu, ale stabilna ocena indeksacji i ruchu organicznego wymaga zwykle kilku dni lub tygodni, zależnie od wielkości serwisu.

Czy po migracji musi nastąpić spadek ruchu?

Profesjonalna migracja na Next.js nie musi wiązać się ze spadkiem ruchu. Wyzwaniem jest utrzymanie ciągłości adresów URL i metadanych. Ewentualne wahania w ciągu pierwszych 2 tygodni są zazwyczaj wynikiem tego, jak szybko boty Google przetworzą mapę przekierowań 301. Celem jest wdrożenie całkowicie przezroczyste dla Google. Bez redirectów to trzęsienie ziemi i tracisz 50–80% ruchu, ponieważ Google uznaje, że stara treść zniknęła, a nowa nie ma żadnej historii.

Czy muszę migrować komentarze?

Raczej sugerowałbym ich eksport, ponieważ są naturalną formą wyrazu obecności użytkowników na Twojej stronie internetowej. Więc można powiedzieć, że jakąś wartość SEO zawsze mają, chyba że wyglądają jak spam.

Czy mogę utrzymać WordPress jako CMS i przenieść frontend?

Tak, jest to podejście headless. WordPress zostaje jako CMS, czyli zarządzanie treścią, a Next.js przejmuje frontend i w ten sposób nie musimy robić migracji treści czy zmieniać struktur workflow redakcyjnego.

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
Migracja z WordPress na Astro: Jak zachować SEO? Przekierowania 301 i eksport treści

WordPress to CMS, z którym prosto zacząć, ale dość szybko pojawia się coraz więcej problemów. Wtyczki spowalniają stronę, a przy okazji bywają furtką dla włamań, Core Web Vitals wymagają głębokiej optymalizacji, a każda aktualizacja to potencjalne ryzyko regresji. Dobrze przeprowadzona migracja na Astro porządkuje te metryki i domyka większość luk — finalnie projekt staje się szybszy, bezpieczniejszy i przede wszystkim stabilniejszy.

Maciej Sala

Maciej Sala

Founder StriveLab

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

Trudno wyobrazić sobie współczesną analitykę bez Google Search Console GSC , czyli podstawowego narzędzia pokazującego dane bezpośrednio z Google Search. PageSpeed Insights mierzy wydajność, Ahrefs śledzi backlinki, ale GSC pokazuje całą paletę danych ale też problemów/błędów dotyczących strony internetowej. Przykładowo, które strony są zaindeksowane, jakie błędy crawlowania występują, na jakie frazy rankujesz i jakie wyniki Core Web Vitals Dane Core Web Vitals w GSC pochodzą z Chrome UX Report, czyli realnych wizyt użytkowników, a nie z pojedynczego testu laboratoryjnego Lighthouse. mają istniejący użytkownicy.

Maciej Sala

Maciej Sala

Founder StriveLab

Next.js czy WordPress w 2026 roku? Co wybrać

Z jednej strony mamy WordPressa — weterana, który wciąż napędza ponad 40% globalnego internetu. Z drugiej Next.js, flagowy framework wśród nowoczesnych technologii opartych na Reakcie. Cel obu narzędzi jest ten sam, by wypuścić stronę w świat. Różnią się jednak diametralnie drogą do tego celu, a wybór między nimi to kwestia tego, co konkretny projekt ma osiągnąć i kto będzie go utrzymywał.

Maciej Sala

Maciej Sala

Founder StriveLab