Migration zu Next.js / Astro in Wiesbaden

Wenn das alte CMS keinen Export kennt: Inhalte aus der Datenbank lesen, die Website als Rückfallweg crawlen, HTML in Markdown oder Felder umwandeln und Adressen wie index.php?id=42 auf sprechende Pfade weiterleiten.

Migration zu Next.js / Astro

Website- & Anwendungsmigration in Wiesbaden

Ich migriere Websites von WordPress, Joomla, Drupal, Angular, Vue und anderen Technologien zu Astro und Next.js. Jede Migration plane ich so, dass SEO, Inhalte und Funktionen erhalten bleiben, und schalte in einem vereinbarten Zeitfenster um. Ich arbeite allein und habe langjährige Erfahrung mit älteren und modernen Stacks.

  • Mein wordpress.org-Konto besteht seit 2006.
  • Ich habe zum WordPress-Core in den Versionen 7.0 und 7.1 beigetragen.
  • Ich betreue 30 Plugins auf wordpress.org.
  • Ich organisiere WordCamps und trete dort als Speaker auf.
  • Ich war bei jedem WordCamp Europe von 2017 bis 2026 dabei.

Mein WordPress.org-Profil

Ihre Website läuft seit Jahren auf einem CMS, das eine Agentur einmal selbst geschrieben hat, oder auf einem System, dessen Hersteller es nicht mehr gibt. Einen Export-Knopf hat es nie gegeben, die Agentur antwortet nicht mehr, und niemand traut sich an den Server. Ich bin Mariusz Szatkowski, ein einzelner Entwickler in Gdynia, der schriftlich und aus der Ferne arbeitet; WordPress begleitet mich seit 2006, Astro und Next.js gehören heute ebenso dazu. Diese Seite beschreibt, wie ich Inhalte aus einem solchen System hole und wie die alten Adressen dabei erhalten bleiben.

Die Landeshauptstadt nennt auf ihrer Wirtschaftsseite Einzelhandel, Handwerksbetriebe, Banken- und Versicherungswirtschaft, Dienstleister und eine Gründerszene. Für alle gilt beim Umzug dieselbe Aufsicht über den Datenschutz, und die sitzt in derselben Stadt, dazu unten mehr.

#Woran Sie erkennen, dass es keinen Exportweg gibt

Ob ein Export fehlt, sehen Sie an drei Dingen. Im Verwaltungsbereich gibt es keine Funktion, die Inhalte als Datei herausgibt. Eine Programmierschnittstelle, über die andere Systeme Seiten abrufen könnten, existiert nicht. Und die Adressen der öffentlichen Seiten sehen aus wie index.php?id=42 oder seite.php?kat=3&art=17, also ohne sprechende Pfade.

Das klingt nach einer Sackgasse, ist aber meistens keine. Das Verwaltungsmenü hat keinen Export, die Datenbank dahinter enthält trotzdem jeden Text. Wenn auch die Datenbank unerreichbar ist, liefert die öffentliche Website das HTML, aus dem sich die Inhalte zurückgewinnen lassen.

#Erster Weg: die Datenbank direkt lesen

Am saubersten ist ein Abzug der Datenbank, den der Hoster oder Ihr Administrator erstellt. Ich arbeite nur mit dieser Kopie, nie mit der laufenden Datenbank, und richte sie lokal ein. Dann sehe ich mir die Tabellen an und beantworte für jede eine Frage: Was davon ist Inhalt, was Navigation, was Einstellung, was Altlast?

In Eigenbau-Systemen wiederholt sich ein Muster. Es gibt eine Tabelle für Seiten mit Titel, Text als HTML, einer numerischen ID und einem Verweis auf die übergeordnete Seite. Daraus lässt sich die Seitenstruktur rekonstruieren. Meta-Beschreibungen liegen manchmal in derselben Tabelle, manchmal in einer eigenen, manchmal fest im Template. Bilder stehen als Dateinamen im Text oder in einer Zuordnungstabelle, die Dateien selbst liegen im Dateisystem des Servers. Deshalb brauche ich neben dem Datenbankabzug auch eine Kopie des Upload-Verzeichnisses.

Häufige Stolpersteine, die ich vor jedem Umwandeln prüfe:

  • Zeichenkodierung. Umlaute, die schon in der Datenbank falsch gespeichert sind, werden durch eine Migration nicht besser. Ich vergleiche Stichproben aus der Datenbank mit der Darstellung auf der Live-Seite.
  • Entwürfe und gelöschte Seiten. Viele Systeme markieren Seiten nur als gelöscht oder unveröffentlicht. Diese Datensätze gehören nicht auf die neue Website, wohl aber in die Liste, falls Links von außen auf sie zeigen.
  • Inhalte in mehreren Sprachen. Steht die Sprache in einer eigenen Spalte oder in einer Kopie der Tabelle, bestimmt das, wie die Pfade der neuen Website aussehen. Mehrsprachige Umzüge behandle ich ausführlich auf der Seite zur Migration mehrsprachiger Websites in Basel.

#Personenbezogene Daten im Abzug

Ein Datenbankabzug enthält selten nur Seiten. Oft liegen dort Formulareinträge, Newsletter-Adressen oder Benutzerkonten aus einem geschützten Bereich. Wenn ich diese Daten erhalte, verarbeite ich sie in Ihrem Auftrag. Art. 28 Abs. 3 DSGVO verlangt dafür einen Vertrag, in dem unter anderem steht, dass ich nur auf Ihre dokumentierte Weisung arbeite und nach Abschluss alle personenbezogenen Daten nach Ihrer Wahl lösche oder zurückgebe (EUR-Lex).

Für Unternehmen in Hessen ist der Hessische Beauftragte für Datenschutz und Informationsfreiheit zuständig, der seinen Dienstsitz in der Wilhelmstraße in Wiesbaden hat (HBDI, Anfahrt). Er stellt eine Formulierungshilfe für einen Auftragsverarbeitungsvertrag bereit, die auf einem Vorschlag des Bayerischen Landesamts für Datenschutzaufsicht und des Thüringer Datenschutzbeauftragten beruht. Der HBDI weist ausdrücklich darauf hin, dass es sich dabei nicht um Standardvertragsklauseln nach Art. 28 Abs. 8 DSGVO handelt.

Noch einfacher ist es, die Daten gar nicht erst zu übergeben. Nach dem Grundsatz der Datenminimierung in Art. 5 Abs. 1 Buchst. c DSGVO bleiben personenbezogene Daten auf das für den Zweck notwendige Maß beschränkt. Für eine Inhaltsmigration brauche ich keine Formulareinträge. Ich bitte deshalb um einen Abzug ohne diese Tabellen oder um eine Liste der Tabellennamen, aus der Sie schriftlich auswählen, was ich bekomme.

#Zweiter Weg: die laufende Website crawlen

Manchmal gibt es keinen Zugang zur Datenbank mehr. Der Hoster verweist auf die frühere Agentur, die Agentur ist aufgelöst. Dann bleibt die öffentliche Website selbst als Quelle. Ich lade sie vollständig herunter und gewinne die Inhalte aus dem HTML.

Ein Werkzeug dafür ist GNU Wget. Laut Handbuch schaltet die Option --mirror Rekursion und Zeitstempel ein und setzt eine unbegrenzte Tiefe. Mit --wait liegt zwischen zwei Abrufen eine Pause, was das Handbuch empfiehlt, um den Server zu schonen. Für Ihre Adressen ist --adjust-extension wichtig: Das Handbuch nennt selbst das Beispiel einer Adresse wie article.cgi?25, die dann als Datei article.cgi?25.html gespeichert wird. Die Abfrageparameter bleiben also im Dateinamen erhalten, und jede heruntergeladene Datei lässt sich ihrer alten Adresse zuordnen.

Ein Crawl findet nur, was verlinkt ist. Seiten, die nur über ein Suchformular, einen Newsletter oder einen alten Flyer erreichbar waren, fehlen. Google empfiehlt in der Anleitung zum Website-Umzug, die Liste alter Adressen aus mehreren Quellen zusammenzusetzen: Sitemaps, Serverprotokolle oder Analysesoftware für die meistbesuchten Adressen und der Bericht über Links in der Search Console. Dieselbe Anleitung erinnert daran, eingebettete Bilder, PDFs und andere Downloads mitzunehmen, weil auch sie Besucher aus der Suche und über Links bekommen.

Russische Kirche auf dem Neroberg in Wiesbaden

Foto: Rüdiger Maas, CC BY-SA 3.0, Wikimedia Commons.

#Vom alten HTML zu Markdown oder Feldern

Ob aus der Datenbank oder aus dem Crawl: Am Ende liegt HTML vor, das ein Editor vor fünfzehn Jahren erzeugt hat. Darin stecken Tabellen für das Layout, Schriftangaben mit <font>, leere Absätze als Abstandshalter und eingefügter Text aus Textverarbeitungen.

Für die Umwandlung in Markdown nutze ich die JavaScript-Bibliothek Turndown. Laut README behält sie standardmäßig kein HTML-Element bei. Mit keep lassen sich einzelne Elemente als HTML erhalten, mit remove samt Inhalt entfernen, und mit addRule kommen eigene Regeln dazu, die vor den Standardregeln greifen. Das README warnt auch, dass die Maskierung von Markdown-Zeichen recht aggressiv ausfallen kann. Eine Überschrift wie 1. Hello world wird zu 1\. Hello world. Ich prüfe deshalb nach jedem Durchlauf Stichproben, statt dem Ergebnis blind zu vertrauen.

Nicht alles gehört in Fließtext. Wiederkehrende Inhalte mit fester Form sind als strukturierte Felder besser aufgehoben:

Alter InhaltNeue FormGrund
Seiten mit FließtextMarkdown-Datei mit Titel und Beschreibung im KopfRedaktion und Versionierung bleiben einfach
Teammitglieder, AnsprechpartnerEinträge in JSON oder YAML mit Name, Funktion, Bildeine Änderung wirkt auf allen Seiten
Stellenanzeigen, TermineEinträge mit Datum und Statusabgelaufene Einträge lassen sich automatisch ausblenden
DownloadsListe mit Datei, Titel, Datumalte Download-Adressen werden mitgeführt

#Wie Astro diese Inhalte lädt

Astro organisiert solche Daten in Content Collections. Laut Loader-Referenz erzeugt der Loader glob() Einträge aus Verzeichnissen mit Markdown-, MDX-, Markdoc-, JSON-, YAML- oder TOML-Dateien. Der Loader file() liest eine einzelne Datei mit einer Liste von Objekten, die jeweils eine eindeutige id haben. Für entfernte Datenquellen bringt Astro keinen fertigen Loader mit. Dafür baut man einen eigenen Objekt-Loader mit einer Methode load(), die Daten holt, mit parseData() gegen ein Zod-Schema prüft und mit store.set() speichert. Ein Hilfsmittel renderMarkdown() wandelt Markdown dabei in HTML um.

Bei einem verwaisten CMS wandle ich die Inhalte trotzdem einmal in Dateien um und lade sie mit glob() oder file(). Ein eigener Loader, der bei jedem Build die alte Datenbank abfragt, hält ein System am Leben, das gerade abgeschaltet werden soll. Nur wenn das alte System für eine Übergangszeit weiter gepflegt wird, verbinde ich es vorübergehend. Wie ein solcher Umzug in Etappen aussieht, steht auf der Seite zum Umzug in Etappen in Köln. Für Next.js liegen dieselben Markdown- und JSON-Dateien im Repository und werden beim Build gelesen.

#Alte Adressen mit index.php?id= auf sprechende Pfade

Google zählt einen Wechsel von example.com/page.php?id=1 zu example.com/widget ausdrücklich zu den Website-Umzügen mit URL-Änderung. In den Empfehlungen zur URL-Struktur rät Google zu lesbaren Wörtern statt langer ID-Nummern und führt index.php?topic=42&area=... als Beispiel auf, das nicht empfohlen wird. Weitere Punkte aus derselben Seite, die bei der neuen Struktur zählen:

  • Wörter mit Bindestrichen trennen, nicht mit Unterstrichen.
  • Wörter in der Sprache des Publikums verwenden; Google zeigt dafür ein deutsches Beispiel wie /lebensmittel/pfefferminz.
  • Zeichen außerhalb von ASCII in Links prozentkodieren, aus gemüse wird gem%C3%BCse.
  • Groß- und Kleinschreibung beachten, denn Google behandelt /APPLE und /apple als verschiedene Adressen.

Für jede alte ID schreibe ich das Ziel in eine Tabelle. Aus der Datenbank kommen ID und Titel, daraus entsteht der neue Pfad, und Sie prüfen die Tabelle, bevor eine Regel live geht. Seiten, die nicht mitkommen, beantworten die neue Website laut Google mit 404 oder 410. Viele alte Adressen pauschal auf die Startseite zu leiten, kann Google als Soft 404 werten. Wie die vollständige Adressliste entsteht und wo Weiterleitungen beim Host liegen, beschreibt die Seite zu den Weiterleitungen in Bremen. Hier geht es um den Sonderfall, dass der Pfad immer gleich ist und nur der Parameter wechselt.

#In Next.js: has mit type query

Die Next.js-Dokumentation erlaubt, eine Weiterleitung an Header, Cookies, Host oder Abfrageparameter zu knüpfen. Dazu dient das Feld has mit type: 'query'. Aus der Mapping-Tabelle entsteht pro ID eine Regel:

// next.config.js
const alteIds = require('./alte-ids.json') // { "42": "/leistungen/umzug", ... }

module.exports = {
  async redirects() {
    return Object.entries(alteIds).map(([id, ziel]) => ({
      source: '/index.php',
      has: [{ type: 'query', key: 'id', value: id }],
      destination: ziel,
      permanent: true,
    }))
  },
}

Mit Next.js 16.4.6 habe ich das lokal geprüft. /index.php?id=42 antwortet mit 308, id=142 und id=420 werden nicht von der Regel für 42 erfasst. Die Dokumentation sagt außerdem, dass Abfragewerte der Anfrage an das Ziel weitergegeben werden. Das Ziel lautet deshalb /leistungen/umzug?id=42. Google empfiehlt für jede neue Adresse einen Canonical-Verweis auf sich selbst, und genau den setze ich auf der Zielseite, damit die saubere Adresse in den Index kommt.

#In Astro: ein Endpunkt, der den Parameter liest

Die Option redirects in Astro ordnet laut Konfigurationsreferenz Routen einander zu. Abfrageparameter kommen darin nicht vor, id=12 und id=13 lassen sich dort also nicht unterscheiden. Ohne Adapter erzeugt ein statischer Build außerdem eine Seite mit Meta-Refresh und keinen Statuscode. Für Parameter-Adressen nutze ich deshalb einen Endpunkt, der auf Anfrage gerendert wird. Die Dokumentation beschreibt, dass die Endung .ts beim Build entfällt, aus index.php.ts wird also die Route /index.php, und dass der Kontext eines Endpunkts eine Funktion redirect() mitbringt:

// src/pages/index.php.ts
import type { APIRoute } from "astro";
import alteIds from "../data/alte-ids.json";

export const prerender = false;

export const GET = (({ request, redirect }) => {
  const id = new URL(request.url).searchParams.get("id") ?? "";
  const ziel = (alteIds as Record<string, string>)[id];
  if (!ziel) return new Response(null, { status: 410 });
  return redirect(ziel, 301);
}) satisfies APIRoute;

Mit Astro 7.4.11 und dem Node-Adapter getestet, leitet /index.php?id=42 mit 301 auf /leistungen/umzug weiter, ohne den alten Parameter. Unbekannte IDs bekommen 410. Der Endpunkt braucht einen Adapter und damit einen Server oder eine Plattform, die Funktionen ausführt. Sind die Regeln auf dem Webserver besser aufgehoben, schreibe ich sie dort hinein. Google nennt serverseitige Weiterleitungen mit 301 oder 308 die Variante, die am zuverlässigsten erkannt wird (Google, Weiterleitungen).

Unabhängig vom Framework gilt Googles Hinweis aus der Umzugsanleitung: Weiterleitungen möglichst lange behalten, in der Regel mindestens ein Jahr. Rankings können während des Umzugs schwanken, und bei mittelgroßen Websites dauert es laut Google einige Wochen oder länger, bis die neuen Adressen in der Suche erscheinen. Ein Versprechen ohne Rankingverlust gebe ich deshalb nicht. Ich messe vorher und nachher.

#Formulare und Logik, die in den Templates stecken

Bei Eigenbau-Systemen liegt nicht alles in der Datenbank. Kontaktformulare schicken E-Mails direkt aus dem Template, ein Preisrechner steckt in einer PHP-Datei, eine Terminliste fragt eine Tabelle ab, die sonst niemand kennt. Diese Funktionen sieht man der Website nicht an. Deshalb lese ich die Templates und Skripte auf dem Server Datei für Datei durch und suche nach Versand von E-Mails, eigenen Datenbankabfragen, eingebundenen Dateien und Formularen, die ihre Daten an ein Skript senden.

Daraus entsteht eine Liste, in der jede Funktion eine Entscheidung bekommt: neu bauen, durch einen Dienst ersetzen oder ersatzlos streichen. Ein Preisrechner, den seit Jahren niemand gepflegt hat, rechnet vielleicht mit alten Werten. Ob er bleibt, entscheiden Sie. Wie Formulare nach dem Umzug auf dem Server laufen, mit Spamschutz und Einwilligung, steht auf der Seite zu Formularen nach der Migration in Bonn. Hatte das alte System eine eigene Suche, finden Sie die Optionen auf der Seite zur Website-Suche in Duisburg. Stammt der alte Code eher aus der JavaScript-Welt als aus PHP, hilft die Seite zu alten JavaScript-Stacks in Dortmund.

#Checkliste vor dem Abschied vom alten System

  1. Wer hat heute Zugang zu Server, Datenbank und Domain, und auf wen läuft der Hostingvertrag?
  2. Gibt es einen aktuellen Datenbankabzug und eine Kopie des Upload-Verzeichnisses?
  3. Welche Tabellen enthalten personenbezogene Daten, und brauche ich sie überhaupt?
  4. Welche Adressformen kommen vor: index.php?id=, weitere Parameter, Sprachkennungen?
  5. Liegen eine Sitemap, Serverprotokolle oder Search-Console-Daten für die Adressliste vor?
  6. Welche Formulare, Rechner, Listen oder Logins stecken in den Templates?
  7. Welche PDFs und Bilder werden von außen verlinkt?
  8. Wer prüft die Mapping-Tabelle, bevor sie live geht?

Wenn Sie Frage 1 nicht beantworten können, ist das der erste Arbeitsschritt, noch vor jedem Framework.

#So läuft die Migration mit mir

Sie schicken mir die Adresse der Website und die Antworten auf die Checkliste, so weit Sie sie kennen. Ich stelle Rückfragen schriftlich und erhalte von Ihnen einen Datenbankabzug, Zugang zum Server oder nur die Erlaubnis zum Crawl. Bei personenbezogenen Daten steht vorher der Vertrag zur Auftragsverarbeitung. Danach bekommen Sie einen schriftlichen Umfang mit Annahmen: welche Inhalte umziehen, welche Funktionen neu entstehen, welche entfallen.

Die neue Website entsteht auf einer Staging-Umgebung, die für Suchmaschinen gesperrt ist. Gestaltung, Mockups und Wireframes bringen Sie für die neue Website mit. Welches Element auf welche Seite gehört, markieren Sie in meiner Tabellenvorlage, danach setze ich es responsiv um. Sie prüfen Inhalte und Mapping-Tabelle und nehmen ab, bevor etwas live geht. Was vor dem Start getestet wird, beschreibt die Seite zum Testplan in Zürich, den Betrieb danach die Seite zur Wartung in Wuppertal. Nach Abschluss lösche ich den Datenbankabzug oder gebe ihn zurück, wie Sie es festlegen. Eine Übersicht über den gesamten Ablauf finden Sie auf der Seite zur Migration zu Next.js und Astro.

#Häufige Fragen

Mein CMS hat keinen Export. Muss ich die Texte abtippen lassen? Nein. Fast jedes CMS speichert seine Inhalte in einer Datenbank, und die lässt sich als Kopie auslesen. Gibt es keinen Zugang mehr zur Datenbank, bleibt der Crawl der laufenden Website. Abgetippt wird höchstens, was in Bildern oder Scans steckt.

Was mache ich, wenn die frühere Agentur nicht mehr erreichbar ist? Dann frage ich zuerst beim Hoster nach Zugängen, denn Vertragspartner für Server und Datenbank sind meist Sie selbst. Klappt das nicht, crawle ich die Website, ergänze die Adressliste aus Sitemap, Serverprotokollen und Search Console und baue die Inhalte daraus nach.

Bleiben meine alten Adressen mit index.php?id= erreichbar? Sie führen per permanenter Weiterleitung auf die neue Adresse. In Next.js geht das mit has und type query in next.config.js, in Astro mit einem Endpunkt, der den Parameter liest. Für jede ID steht das Ziel in einer Tabelle, die Sie vor dem Start prüfen.

Warum taucht nach der Weiterleitung noch ?id=42 in der Adresszeile auf? Next.js gibt Abfragewerte laut Dokumentation an das Ziel weiter. Das ist kein Fehler, solange die neue Seite einen Canonical auf ihre eigene saubere Adresse setzt, wie Google es für Umzüge empfiehlt. In Astro bestimme ich das Ziel im Endpunkt selbst, dort entfällt der Parameter.

Brauche ich mit Ihnen einen Vertrag zur Auftragsverarbeitung? Ja, sobald ich eine Datenbank mit personenbezogenen Daten bekomme, etwa Formulareinträge oder Benutzerkonten. Der HBDI bietet eine Formulierungshilfe für einen solchen Vertrag nach Art. 28 Abs. 3 DSGVO. Einfacher ist ein Dump ohne diese Tabellen, wenn ich sie nicht brauche.

Gestalten Sie die neue Website auch? Nein. Gestaltung, Mockups und Wireframes bringen Sie für die neue Website mit. Welches Element auf welche Seite gehört, markieren Sie in meiner Tabellenvorlage, danach baue ich es responsiv in Astro oder Next.js.

#Schreiben Sie mir, solange das alte System noch läuft

Je länger ein verwaistes CMS ohne Pflege läuft, desto größer wird das Risiko, dass Server oder Datenbank eines Tages nicht mehr starten. Wenn Ihre Website in Wiesbaden auf einem solchen System steht, schicken Sie mir über das Kontaktformular die Adresse, Ihr Ziel, einen gewünschten Termin und was Sie über den heutigen Aufbau wissen: Hoster, Zugänge, bekannte Formulare und ob ein Datenbankabzug vorliegt. Auch eine unvollständige Antwort reicht für den Anfang. Sie bekommen von mir eine schriftliche Ersteinschätzung ohne Kosten, mit dem Weg, der für Ihren Bestand passt: Datenbank, Crawl oder beides.

Brauchen Sie die Leistung: Migration zu Next.js / Astro in Wiesbaden?

Beschreiben Sie Ihr Projekt per E-Mail. Antwort innerhalb von 48 Stunden.

Projekt beschreiben

Weitere WordPress-Dienste und Wissensbasis entdecken