Die 3D-Insel funktionierte bereits. Als Nächstes wollte ich einen Blog, eine Leistungsseite und eine eigene Adresse für jede Seite auf Deutsch und Englisch.
Am 6. Juli 2026 habe ich dieses Portfolio von einer Vite-Single-Page-App auf den Next.js App Router umgestellt. Die wichtigste Entscheidung dabei war, welche Aufgaben auf den Server gehören und welche weiterhin den Browser brauchen.
Aktualisiert am 2. Oktober 2026: Dieser Artikel beschreibt die ursprüngliche Migration zu Next.js 14. Seit September läuft die Website mit Next.js 15, React 19 und React Three Fiber 9. Die Erklärungen unterscheiden jetzt zwischen diesem ersten Schritt und dem heutigen Stack.
Eine eigene Adresse für jede Seite
Die Vite-Version nutzte React Router und i18next. Die Sprachwahl lag in localStorage. Die Routen teilten sich ein HTML-Grundgerüst mit allgemeinen Metadaten; ihre Inhalte erschienen nach dem Ausführen von JavaScript. Deutsch und Englisch hatten keine eigenen URL-Strukturen.
Das machte das Veröffentlichen unnötig umständlich. Ein Link zu einem deutschen Artikel sollte zuverlässig genau diesen Artikel öffnen. Titel, Beschreibung und Inhalt sollten bereits in der HTML-Antwort stehen.
Der App Router gab mir diese Struktur: /de/... und /en/..., Metadaten pro Seite und hreflang-Verweise zwischen den Übersetzungen. Blogartikel liegen als MDX-Dateien vor und werden beim Build zu HTML gerendert.
Google kann JavaScript ausführen. Die alte Seite war deshalb nicht automatisch unsichtbar für die Suche. Der Vorteil ist, dass das Lesen der Inhalte diesen zusätzlichen Schritt nicht mehr braucht. Andere Crawler führen unter Umständen gar kein JavaScript aus. Googles Leitfaden zu JavaScript und SEO erklärt den Unterschied.
Framework-Wechsel und 3D-Upgrade trennen
Die Szene nutzte React Three Fiber 8 und React 18. Diese Versionen gehören zusammen: Die R3F-Dokumentation ordnet Version 8 React 18 zu und Version 9 React 19.
Für den ersten Umzug auf Next.js 14 habe ich dieses Paar beibehalten. So gab es weniger gleichzeitige Änderungen, die bei einem Fehler in der Szene als Ursache infrage kamen. Das Upgrade auf React 19 und R3F 9 folgte später gemeinsam mit Next.js 15.
Diese Entscheidung würde ich bei einer ähnlichen Migration wieder treffen: Den funktionierenden Renderer stabil halten, während sich die Anwendung darum verändert.
Drei Details, die Aufmerksamkeit brauchten
Werte aus dem Browser. Die alte Szene las beim Rendern window.innerWidth, um ihre Größe festzulegen. Auf dem Server gibt es kein window. Ich habe die Messung in einen Effect verschoben und die interaktive Szene in einer Client-Komponente hinter einer serverseitig gerenderten Seite belassen.
Modell-URLs. Das Vite-Setup importierte .glb-Dateien als URLs. Im Next.js-Setup liegen die Modelle unter public/3d/ und werden über ihre öffentlichen Pfade geladen. So brauchte ich keine eigene Konfiguration für einen Asset-Loader. Die Website setzt außerdem immutable Cache-Header für diese Dateien. Ein geändertes Modell braucht deshalb einen neuen Dateinamen, damit Besucher*innen keine veraltete Kopie erhalten.
Sprachwechsel. Mit next-intl wechselt die Sprache in der URL, während der Seitenpfad erhalten bleibt. Die Middleware merkt sich die Präferenz in einem Cookie. Schon die URL zeigt, welche Sprachversion geöffnet wird.
Was der Umzug gebracht hat
Die Migration hat das interaktive Portfolio erhalten und einen einfachen Veröffentlichungsablauf ergänzt. Artikel und Leistungsbeschreibungen kommen als lesbares HTML an, jede Übersetzung hat eine Adresse, und die 3D-Szene läuft dort, wo sie hingehört: im Browser.
Auch mit Vite lässt sich ein Blog bauen. Für diese Website hat Next.js Routing, Inhaltsdarstellung und Metadaten in einer Struktur zusammengeführt, die ich gut pflegen kann. Das war für mich ein ausreichender Grund für den Wechsel.
