Menu

Migration von Vite

Diese Anleitung hilft dir bei der Migration einer bestehenden Vite-Anwendung zu Next.js.

Warum wechseln?

Es gibt mehrere Gründe, warum ein Wechsel von Vite zu Next.js sinnvoll sein kann:

Langsame anfängliche Seitenladezeit

Wenn du deine Anwendung mit dem Standard-Vite-Plugin für React erstellt hast, ist deine Anwendung eine reine Client-seitige Anwendung. Solche Client-seitigen Anwendungen, auch bekannt als Single-Page-Applications (SPAs), haben oft eine langsame anfängliche Seitenladezeit. Dies geschieht aus mehreren Gründen:

  1. Der Browser muss warten, bis der React-Code und das gesamte Anwendungs-Bundle heruntergeladen und ausgeführt wurden, bevor dein Code Anfragen zum Laden von Daten senden kann.
  2. Dein Anwendungscode wächst mit jeder neuen Funktion und zusätzlichen Abhängigkeit.

Kein automatisches Code-Splitting

Das vorherige Problem der langsamen Ladezeiten kann durch Code-Splitting teilweise behoben werden. Wenn du jedoch versuchst, Code-Splitting manuell durchzuführen, verschlechterst du häufig die Performance. Es ist leicht, versehentlich Network-Waterfalls einzuführen, wenn du Code-Splitting manuell durchführst. Next.js bietet automatisches Code-Splitting, das in seinen Router integriert ist.

Network-Waterfalls

Eine häufige Ursache für schlechte Performance sind sequentielle Client-Server-Anfragen zum Laden von Daten. Ein gängiges Muster für das Datenabrufen in einer SPA ist das anfängliche Rendern eines Platzhalters und das anschließende Abrufen von Daten nach dem Mounting der Komponente. Dies bedeutet jedoch, dass eine untergeordnete Komponente, die Daten abruft, erst mit dem Abrufen beginnen kann, wenn die übergeordnete Komponente ihre eigenen Daten geladen hat.

Während das Abrufen von Daten auf dem Client mit Next.js unterstützt wird, bietet es auch die Möglichkeit, das Datenabrufen auf den Server zu verlagern, wodurch Client-Server-Waterfalls eliminiert werden können.

Schnelle und durchdachte Ladezustände

Mit der eingebauten Unterstützung für Streaming durch React Suspense kannst du gezielter bestimmen, welche Teile deiner UI du zuerst und in welcher Reihenfolge laden möchtest, ohne Network-Waterfalls einzuführen.

Dies ermöglicht dir, Seiten zu erstellen, die schneller laden und Layout-Shifts eliminieren.

Wähle die Datenabruf-Strategie

Je nach Bedarf ermöglicht dir Next.js die Wahl der Datenabruf-Strategie auf Seiten- und Komponentenbasis. Du kannst entscheiden, ob du Daten zur Build-Zeit, zur Anforderungszeit auf dem Server oder auf dem Client abrufen möchtest. Du kannst beispielsweise Daten von deinem CMS abrufen und deine Blogbeiträge zur Build-Zeit rendern, die dann effizient auf einem CDN gecacht werden können.

Middleware

Next.js Middleware ermöglicht es dir, Code auf dem Server auszuführen, bevor eine Anfrage abgeschlossen wird. Dies ist besonders nützlich, um zu vermeiden, dass nicht authentifizierte Inhalte kurz angezeigt werden, wenn ein Benutzer eine authentifizierungspflichtige Seite besucht, indem der Benutzer zur Login-Seite umgeleitet wird. Die Middleware ist auch nützlich für Experimente und Internationalisierung.

Eingebaute Optimierungen

Bilder, Schriftarten und Skripte von Drittanbietern haben oft erhebliche Auswirkungen auf die Performance einer Anwendung. Next.js enthält eingebaute Komponenten, die diese automatisch für dich optimieren.

Migrations-Schritte

Unser Ziel bei dieser Migration ist es, so schnell wie möglich eine funktionierende Next.js-Anwendung zu erstellen, damit du dann schrittweise Next.js-Funktionen einführen kannst. Zunächst behalten wir sie als reine Client-seitige Anwendung (SPA) bei, ohne deinen bestehenden Router zu migrieren. Dies minimiert die Wahrscheinlichkeit von Problemen während des Migrationsprozesses und reduziert Merge-Konflikte.

Schritt 1: Next.js-Abhängigkeit installieren

Als Erstes musst du next als Abhängigkeit installieren:

Terminal
npm install next@latest

Schritt 2: Next.js-Konfigurationsdatei erstellen

Erstelle eine next.config.mjs im Hauptverzeichnis deines Projekts. Diese Datei enthält deine Next.js-Konfigurationsoptionen.

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // Erstellt eine Single-Page Application (SPA).
  distDir: './dist', // Ändert das Build-Ausgabeverzeichnis zu `./dist/`.
}
 
export default nextConfig

Hinweis: Du kannst entweder .js oder .mjs für deine Next.js-Konfigurationsdatei verwenden.

Schritt 3: TypeScript-Konfiguration aktualisieren

Wenn du TypeScript verwendest, musst du deine tsconfig.json-Datei mit den folgenden Änderungen aktualisieren, um sie mit Next.js kompatibel zu machen. Wenn du kein TypeScript verwendest, kannst du diesen Schritt überspringen.

  1. Entferne die Projektreferenz zu tsconfig.node.json
  2. Füge ./dist/types/**/*.ts und ./next-env.d.ts zum include-Array hinzu
  3. Füge ./node_modules zum exclude-Array hinzu
  4. Füge { "name": "next" } zum plugins-Array in compilerOptions hinzu: "plugins": [{ "name": "next" }]
  5. Setze esModuleInterop auf true: "esModuleInterop": true
  6. Setze jsx auf preserve: "jsx": "preserve"
  7. Setze allowJs auf true: "allowJs": true
  8. Setze forceConsistentCasingInFileNames auf true: "forceConsistentCasingInFileNames": true
  9. Setze incremental auf true: "incremental": true

Hier ist ein Beispiel einer funktionierenden tsconfig.json mit diesen Änderungen:

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "allowJs": true,
    "forceConsistentCasingInFileNames": true,
    "incremental": true,
    "plugins": [{ "name": "next" }]
  },
  "include": ["./src", "./dist/types/**/*.ts", "./next-env.d.ts"],
  "exclude": ["./node_modules"]
}

Weitere Informationen zur Konfiguration von TypeScript findest du in der Next.js-Dokumentation.

Schritt 4: Root Layout erstellen

Eine Next.js App Router-Anwendung muss ein Root Layout enthalten, das eine React Server Component ist und alle Seiten deiner Anwendung umschließt. Diese Datei wird auf der obersten Ebene des app-Verzeichnisses definiert.

Das nächste Äquivalent zur Root Layout-Datei in einer Vite-Anwendung ist die index.html-Datei, die deine <html>, <head> und <body>-Tags enthält.

In diesem Schritt wandelst du deine index.html-Datei in eine Root Layout-Datei um:

  1. Erstelle ein neues app-Verzeichnis in deinem src-Verzeichnis.
  2. Erstelle eine neue layout.tsx-Datei in diesem app-Verzeichnis:
app/layout.tsx
TypeScript
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return '...'
}

Hinweis: Für Layout-Dateien können die Dateierweiterungen .js, .jsx oder .tsx verwendet werden.

  1. Kopiere den Inhalt deiner index.html-Datei in die zuvor erstellte <RootLayout>-Komponente und ersetze die body.div#root und body.script-Tags durch <div id="root">{children}</div>:
app/layout.tsx
TypeScript
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <link rel="icon" type="image/svg+xml" href="/icon.svg" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
  1. Next.js enthält standardmäßig bereits die meta charset- und meta viewport-Tags, sodass du diese aus deinem <head> sicher entfernen kannst:
app/layout.tsx
TypeScript
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <link rel="icon" type="image/svg+xml" href="/icon.svg" />
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
  1. Alle Metadaten-Dateien wie favicon.ico, icon.png, robots.txt werden automatisch zum <head>-Tag der Anwendung hinzugefügt, sofern sie sich im obersten Verzeichnis des app-Verzeichnisses befinden. Nach dem Verschieben aller unterstützten Dateien in das app-Verzeichnis kannst du ihre <link>-Tags sicher löschen:
app/layout.tsx
TypeScript
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
  1. Schließlich kann Next.js deine letzten <head>-Tags mit der Metadata API verwalten. Verschiebe deine restlichen Metadaten-Informationen in ein exportiertes metadata-Objekt:
app/layout.tsx
TypeScript
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'My App',
  description: 'My App is a...',
}
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}

Mit den obigen Änderungen hast du von der Deklaration in deiner index.html zu Next.js' konventionsbasiertem Ansatz gewechselt, der in das Framework integriert ist (Metadata API). Dieser Ansatz ermöglicht es dir, die SEO und Web-Teilbarkeit deiner Seiten einfacher zu verbessern.

Schritt 5: Einstiegspunkt-Seite erstellen

In Next.js deklarierst du einen Einstiegspunkt für deine Anwendung durch Erstellen einer page.tsx-Datei. Das nächste Äquivalent zu dieser Datei in Vite ist deine main.tsx-Datei. In diesem Schritt richtest du den Einstiegspunkt deiner Anwendung ein.

  1. Erstelle ein [[...slug]]-Verzeichnis in deinem app-Verzeichnis.

Da wir in dieser Anleitung zunächst unsere Next.js-Anwendung als SPA (Single Page Application) einrichten möchten, muss dein Seiten-Einstiegspunkt alle möglichen Routen deiner Anwendung abfangen. Erstelle dafür ein neues [[...slug]]-Verzeichnis in deinem app-Verzeichnis.

Dieses Verzeichnis ist ein sogenanntes optionales Catch-all Route Segment. Next.js verwendet einen dateibasierte Router, bei dem Verzeichnisse zum Definieren von Routen verwendet werden. Dieses spezielle Verzeichnis stellt sicher, dass alle Routen deiner Anwendung zu seiner enthaltenen page.tsx-Datei geleitet werden.

  1. Erstelle eine neue page.tsx-Datei im Verzeichnis app/[[...slug]] mit folgendem Inhalt:
app/[[...slug]]/page.tsx
TypeScript
import '../../index.css'
 
export function generateStaticParams() {
  return [{ slug: [''] }]
}
 
export default function Page() {
  return '...' // Werden wir aktualisieren
}

Hinweis: Für Seiten-Dateien können die Dateierweiterungen .js, .jsx oder .tsx verwendet werden.

Diese Datei ist eine Server Component. Wenn du next build ausführst, wird die Datei in ein statisches Asset vorgerendert. Sie erfordert keinen dynamischen Code.

Diese Datei importiert unser globales CSS und teilt generateStaticParams mit, dass wir nur eine Route generieren werden, die Index-Route unter /.

Nun verschieben wir den Rest unserer Vite-Anwendung, die nur Client-seitig ausgeführt wird.

app/[[...slug]]/client.tsx
TypeScript
'use client'
 
import React from 'react'
import dynamic from 'next/dynamic'
 
const App = dynamic(() => import('../../App'), { ssr: false })
 
export function ClientOnly() {
  return <App />
}

Diese Datei ist eine Client Component, definiert durch die 'use client'-Direktive. Client Components werden immer noch zu HTML vorgerendert auf dem Server, bevor sie an den Client gesendet werden.

Da wir zu Beginn eine reine Client-Anwendung wollen, können wir Next.js so konfigurieren, dass das Vorrendern ab der App-Komponente deaktiviert wird.

const App = dynamic(() => import('../../App'), { ssr: false })

Aktualisiere nun deine Einstiegspunkt-Seite, um die neue Komponente zu verwenden:

app/[[...slug]]/page.tsx
TypeScript
import '../../index.css'
import { ClientOnly } from './client'
 
export function generateStaticParams() {
  return [{ slug: [''] }]
}
 
export default function Page() {
  return <ClientOnly />
}

Schritt 6: Statische Bild-Importe aktualisieren

Next.js behandelt statische Bild-Importe etwas anders als Vite. Mit Vite gibt der Import einer Bilddatei deren öffentliche URL als String zurück:

App.tsx
import image from './img.png' // `image` wird '/assets/img.2d8efhg.png' in der Produktion
 
export default function App() {
  return <img src={image} />
}

Mit Next.js geben statische Bild-Importe ein Objekt zurück. Das Objekt kann dann direkt mit der Next.js <Image>-Komponente verwendet werden, oder du kannst die src-Eigenschaft des Objekts mit deinem bestehenden <img>-Tag verwenden.

Die <Image>-Komponente bietet die zusätzlichen Vorteile der automatischen Bildoptimierung. Die <Image>-Komponente setzt automatisch die width- und height-Attribute des resultierenden <img> basierend auf den Bildabmessungen. Dies verhindert Layout-Shifts beim Laden des Bildes. Dies kann jedoch zu Problemen führen, wenn deine App Bilder enthält, bei denen nur eine ihrer Dimensionen gestylt ist, ohne dass die andere auf auto gesetzt ist. Wenn nicht auf auto gestylt, wird die Dimension standardmäßig auf den Wert des <img>-Dimensionsattributs gesetzt, was dazu führen kann, dass das Bild verzerrt erscheint.

Die Beibehaltung des <img>-Tags reduziert die Anzahl der Änderungen in deiner Anwendung und verhindert die oben genannten Probleme. Du kannst dann optional später zur <Image>-Komponente migrieren, um von der Bildoptimierung zu profitieren, indem du einen Loader konfigurierst oder zum Standard-Next.js-Server wechselst, der automatische Bildoptimierung bietet.

  1. Konvertiere absolute Import-Pfade für Bilder aus /public in relative Importe:
// Vorher
import logo from '/logo.png'
 
// Nachher
import logo from '../public/logo.png'
  1. Übergib die src-Eigenschaft des Bildes anstelle des gesamten Bildobjekts an dein <img>-Tag:
// Vorher
<img src={logo} />
 
// Nachher
<img src={logo.src} />

Alternativ kannst du die öffentliche URL für das Bild-Asset basierend auf dem Dateinamen referenzieren. Zum Beispiel wird public/logo.png das Bild unter /logo.png für deine Anwendung bereitstellen, was der src-Wert wäre.

Warnung: Wenn du TypeScript verwendest, könntest du auf Typfehler stoßen, wenn du auf die src-Eigenschaft zugreifst. Du kannst diese vorerst ignorieren. Sie werden am Ende dieser Anleitung behoben.

Schritt 7: Umgebungsvariablen migrieren

Next.js unterstützt .env Umgebungsvariablen ähnlich wie Vite. Der Hauptunterschied ist das Präfix, das verwendet wird, um Umgebungsvariablen auf der Client-Seite verfügbar zu machen.

  • Ändere alle Umgebungsvariablen mit dem Präfix VITE_ zu NEXT_PUBLIC_.

Vite stellt einige eingebaute Umgebungsvariablen über das spezielle import.meta.env-Objekt bereit, die von Next.js nicht unterstützt werden. Du musst ihre Verwendung wie folgt aktualisieren:

  • import.meta.env.MODEprocess.env.NODE_ENV
  • import.meta.env.PRODprocess.env.NODE_ENV === 'production'
  • import.meta.env.DEVprocess.env.NODE_ENV !== 'production'
  • import.meta.env.SSRtypeof window !== 'undefined'

Next.js bietet auch keine eingebaute BASE_URL-Umgebungsvariable. Du kannst jedoch trotzdem eine konfigurieren, wenn du sie benötigst:

  1. Füge Folgendes zu deiner .env-Datei hinzu:
.env
# ...
NEXT_PUBLIC_BASE_PATH="/some-base-path"
  1. Setze basePath auf process.env.NEXT_PUBLIC_BASE_PATH in deiner next.config.mjs-Datei:
next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // Erstellt eine Single-Page Application (SPA).
  distDir: './dist', // Ändert das Build-Ausgabeverzeichnis zu `./dist/`.
  basePath: process.env.NEXT_PUBLIC_BASE_PATH, // Setzt den Basis-Pfad auf `/some-base-path`.
}
 
export default nextConfig
  1. Aktualisiere die Verwendung von import.meta.env.BASE_URL zu process.env.NEXT_PUBLIC_BASE_PATH

Schritt 8: Skripte in package.json aktualisieren

Du solltest jetzt in der Lage sein, deine Anwendung auszuführen, um zu testen, ob du erfolgreich zu Next.js migriert hast. Aber zuvor musst du deine scripts in deiner package.json mit Next.js-bezogenen Befehlen aktualisieren und .next und next-env.d.ts zu deiner .gitignore hinzufügen:

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}
.gitignore
# ...
.next
next-env.d.ts
dist

Führe jetzt npm run dev aus und öffne http://localhost:3000. Du solltest deine Anwendung nun auf Next.js laufen sehen.

Beispiel: Schau dir diesen Pull Request für ein funktionierendes Beispiel einer zu Next.js migrierten Vite-Anwendung an.

Schritt 9: Aufräumen

Du kannst nun deine Codebasis von Vite-bezogenen Artefakten bereinigen:

  • Lösche main.tsx
  • Lösche index.html
  • Lösche vite-env.d.ts
  • Lösche tsconfig.node.json
  • Lösche vite.config.ts
  • Deinstalliere Vite-Abhängigkeiten

Nächste Schritte

Wenn alles nach Plan gelaufen ist, hast du jetzt eine funktionierende Next.js-Anwendung, die als Single-Page-Application läuft. Du nutzt jedoch noch nicht die meisten Vorteile von Next.js, aber du kannst jetzt beginnen, schrittweise Änderungen vorzunehmen, um alle Vorteile zu nutzen. Hier sind einige Dinge, die du als Nächstes tun könntest: