WordPress → Next.js — migracja treści, redirecty 301 i zachowanie pozycji SEO

Jak przenieść stronę z WordPress na Next.js bez utraty pozycji w Google? Eksport treści, mapowanie URL, redirecty 301, migracja obrazów i weryfikacja indeksacji.

Opublikowano

11 kwietnia 2026 10:50

Czytanie

3 min czytania

Aktualizacja

14 kwietnia 2026 13:06

Dlaczego migracja z WordPress jest ryzykowna dla SEO

Podstawową kwestią są i będą URL-e, które Google zaindeksował, przypisał im autorytet i rankuje je na konkretne frazy. Bez ich odpowiedniego zabezpieczenia, zmiana platformy może oznaczać całkowicie utracone pozycje (Google widzi nowe URL-e jako nowe strony), błędy 404 (stare URL-e przestają działać), utratę backlinków (linki zewnętrzne prowadzą donikąd) i spadek ruchu organicznego na tygodnie lub miesiące.

To praktycznie rzecz biorąc coś na kształt trzęsienia ziemi, które niemal doszczętnie burzy Twoją stronę internetową, nawet jeśli od strony technicznej będzie "normalnie" funkcjonować. Odpowiedni plan i przeprowadzenie migracji jest kluczem do sukcesu Twojej strony.

Plan migracji — 7 kroków

Krok 1: Inwentaryzacja tego co aktualnie masz w WordPress

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 musisz stwórzyć nowy arkusz z wymienionymi kolumnami: stary URL, nowy URL, status (migruj/redirect/usuń) oraz priorytet (ruch organiczny).

Krok 2: Eksport treści z WordPress

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

Alternatywnie: WordPress REST API:

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 na MDX (lub import do CMS)

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)}'
date: '${post.date}'
author: 'Maciej Sala'
tags: [${tags.map((t) => `'${t}'`).join(', ')}]
image: '${image}'
---`
 
  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 redirecty 301

Kluczowym zadaniem jest zapewnienie, że każdy stary URL ma swój odpowiednik w nowej witrynie. Jeśli to możliwe, zachowaj strukturę URL. Jeśli zmiana jest nieunikniona, musisz skonfigurować przekierowania 301 (permanent redirect) ze starych adresów na nowe.

Code
// Typowe mapowanie WordPress → Next.js
const urlMap: Record<string, string> = {
  // 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: '/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: '/images/:path*', // Jeśli przeniosłeś media
        permanent: true,
      },
      // Konkretne strony z innym URL
      {
        source: '/o-nas',
        destination: '/o-mnie',
        permanent: true,
      },
    ]
  },
}

Krok 5: Migracja obrazów

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, kolejna na zaktualizowanie lokalnych linków do obrazków w MDX:

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 przed przełączeniem

Lista działań do "odchaczenia" wygląda następująco:

  • Każdy stary URL posiada redirect 301 lub odpowiednik w nowej strukturze,
  • Sitemap zawiera wszystkie zmigrowane strony,
  • Meta tagi (title, description) są poprawnie zachowane z WordPressa,
  • Obrazy zostały przeniesione i są dostępne,
  • Schema.org / JSON-LD zostały poprawnie zachowane lub odtworzone,
  • Canonical 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 i monitoring

  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 GA4 przez 4-6 tygodni.

Spadek ruchu o 10–20% w pierwszym tygodniu jest normalny, ponieważ Google musi wszystko przeindeksować. Sytuacja powinna się poprawiać w przeciągu kolejnych 2–6 tygodni.

Podsumowanie

Migracja WordPress → Next.js wymaga odpowiedniego planowania SEO, czyli jak pisałem: eksport treści, mapowanie URL-i, redirecty 301, migracja obrazów i monitoring indeksacji. Kluczową sprawą jest, by żaden stary URL nie zwrócał błędu 404 — każdy URL musi mieć redirect lub swój odpowiednik.

Podstawowym narzędziem monitorującym proces migracji jest Google Search Console - to Twoje oczy po migracji. Musisz monitorować indeksację, wszelkie pojawiające się błędy oraz ruch na stronie. W tego typu sprawach, zainwestowany w migrację czas, zwraca się w dłuższej perspektywie.

Najczęściej zadawane pytania

Jak długo trwa spadek ruchu po migracji?

Z redirectami (i tymi samymi URL) spadek o 5–15% to czas, w którym algorytm sprawdza, czy nowy kod (Next.js) renderuje tę samą treść i czy jest stabilny. Trwa to zazwyczaj 2–4 tygodnie. 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 (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.

Pracuję z tym zawodowo.

Jeśli chcesz dobrze zrozumieć, jak połączyć SEO, analitykę i warstwę techniczną strony bez zgadywania i półśrodków, skontaktuj się ze mną. Pomagam przekładać wiedzę z takich wpisów na sensowny setup wdrożeniowy.

O autorze

Maciej Sala

Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.

Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów