Migration von Create React App

Diese Anleitung hilft Ihnen, eine bestehende Create-React-App-Website zu Next.js zu migrieren.

Gründe für den Wechsel

Es gibt mehrere Gründe, warum Sie von Create React App zu Next.js wechseln möchten:

Langsame anfängliche Ladezeit

Create React App verwendet rein clientseitiges React. Clientseitige Anwendungen, auch bekannt als Single-Page Applications (SPAs), haben oft eine langsame anfängliche Ladezeit. Dies geschieht aus folgenden Gründen:

  1. Der Browser muss warten, bis der React-Code und Ihr gesamtes Anwendungs-Bundle heruntergeladen und ausgeführt wurden, bevor Ihr Code Anfragen zum Laden von Daten senden kann.
  2. Ihr Anwendungscode wächst mit jeder neuen Funktion und Abhängigkeit, die Sie hinzufügen.

Kein automatisches Code-Splitting

Das vorherige Problem der langsamen Ladezeiten kann teilweise mit Code-Splitting gelöst werden. Wenn Sie jedoch versuchen, Code-Splitting manuell durchzuführen, verschlechtern Sie oft die Performance. Es ist einfach, unbeabsichtigt Netzwerk-Wasserfälle (network waterfalls) bei manuellem Code-Splitting einzuführen. Next.js bietet automatisches Code-Splitting, das in seinen Router integriert ist.

Netzwerk-Wasserfälle

Eine häufige Ursache für schlechte Performance tritt auf, wenn Anwendungen sequenzielle Client-Server-Anfragen zum Abrufen von Daten stellen. Ein gängiges Muster für das Datenabrufen in einer SPA ist, zunächst einen Platzhalter zu rendern und dann Daten abzurufen, nachdem die Komponente gemountet wurde. Leider bedeutet dies, dass eine Kindkomponente, die Daten abruft, erst mit dem Abruf beginnen kann, nachdem die Elternkomponente ihre eigenen Daten fertig geladen hat.

Während das Abrufen von Daten auf dem Client mit Next.js unterstützt wird, haben Sie auch die Möglichkeit, das Datenabrufen auf den Server zu verlagern, was Client-Server-Wasserfälle eliminieren kann.

Schnelle und gezielte Ladezustände

Mit integrierter Unterstützung für Streaming durch React Suspense können Sie gezielter festlegen, welche Teile Ihrer Benutzeroberfläche zuerst und in welcher Reihenfolge geladen werden sollen, ohne Netzwerk-Wasserfälle einzuführen.

Dies ermöglicht es Ihnen, Seiten zu erstellen, die schneller laden und Layoutverschiebungen vermeiden.

Wählen Sie die Datenabrufstrategie

Je nach Bedarf ermöglicht Next.js Ihnen, Ihre Datenabrufstrategie auf Seiten- und Komponentenbasis zu wählen. Sie können entscheiden, ob Sie zum Build-Zeitpunkt, bei der Anfrage auf dem Server oder auf dem Client abrufen möchten. Beispielsweise können Sie Daten aus Ihrem CMS abrufen und Ihre Blogbeiträge zum Build-Zeitpunkt rendern, die dann effizient auf einem CDN zwischengespeichert werden können.

Middleware

Next.js Middleware ermöglicht es Ihnen, Code auf dem Server auszuführen, bevor eine Anfrage abgeschlossen ist. Dies ist besonders nützlich, um ein Aufblitzen von nicht authentifizierten Inhalten zu vermeiden, wenn der Benutzer eine nur für authentifizierte Benutzer bestimmte Seite besucht, indem der Benutzer zu einer Login-Seite weitergeleitet wird. Die Middleware ist auch nützlich für Experimente und Internationalisierung.

Integrierte Optimierungen

Bilder, Schriftarten und Skripte von Drittanbietern haben oft einen erheblichen Einfluss auf die Performance einer Anwendung. Next.js kommt mit integrierten Komponenten, die diese automatisch für Sie optimieren.

Migrationsschritte

Unser Ziel bei dieser Migration ist es, so schnell wie möglich eine funktionierende Next.js-Anwendung zu erhalten, damit Sie dann schrittweise Next.js-Funktionen übernehmen können. Zunächst belassen wir es als rein clientseitige Anwendung (SPA), ohne Ihren bestehenden Router zu migrieren. Dies hilft, die Wahrscheinlichkeit von Problemen während des Migrationsprozesses zu minimieren und Merge-Konflikte zu reduzieren.

Schritt 1: Next.js-Abhängigkeit installieren

Als Erstes müssen Sie next als Abhängigkeit installieren:

Terminal
npm install next@latest

Schritt 2: Next.js-Konfigurationsdatei erstellen

Erstellen Sie eine next.config.mjs im Stammverzeichnis Ihres Projekts. Diese Datei enthält Ihre Next.js-Konfigurationsoptionen.

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

export default nextConfig

Schritt 3: TypeScript-Konfiguration aktualisieren

Wenn Sie TypeScript verwenden, müssen Sie Ihre tsconfig.json-Datei mit den folgenden Änderungen aktualisieren, um sie mit Next.js kompatibel zu machen:

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "baseUrl": ".",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "strictNullChecks": true
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts",
    "./dist/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

Weitere Informationen zur Konfiguration von TypeScript finden Sie in den Next.js-Dokumenten.

Schritt 4: Root-Layout erstellen

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

Die nächste Entsprechung zur Root-Layout-Datei in einer CRA-Anwendung ist die index.html-Datei, die Ihre <html>, <head> und <body>-Tags enthält.

In diesem Schritt konvertieren Sie Ihre index.html-Datei in eine Root-Layout-Datei:

  1. Erstellen Sie ein neues app-Verzeichnis in Ihrem src-Verzeichnis.
  2. Erstellen Sie eine neue layout.tsx-Datei in diesem app-Verzeichnis:
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return null
}
export default function RootLayout({ children }) {
  return null
}

Gut zu wissen: Für Layout-Dateien können die Erweiterungen .js, .jsx oder .tsx verwendet werden.

Kopieren Sie den Inhalt Ihrer index.html-Datei in die zuvor erstellte <RootLayout>-Komponente, während Sie die body.div#root und body.script-Tags durch <div id="root">{children}</div> ersetzen:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}

Gut zu wissen: Wir werden die Manifest-Datei, zusätzliche Icons außer dem Favicon und die Testkonfiguration ignorieren, aber wenn diese erforderlich sind, unterstützt Next.js auch diese Optionen.

Schritt 5: Metadaten

Next.js enthält standardmäßig bereits die meta charset- und meta viewport-Tags, sodass Sie diese sicher aus Ihrem <head> entfernen können:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}

Alle Metadaten-Dateien wie favicon.ico, icon.png, robots.txt werden automatisch zum <head>-Tag der Anwendung hinzugefügt, solange Sie sie auf der obersten Ebene des app-Verzeichnisses platziert haben. Nachdem Sie alle unterstützten Dateien in das app-Verzeichnis verschoben haben, können Sie deren <link>-Tags sicher löschen:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}

Schließlich kann Next.js Ihre letzten <head>-Tags mit der Metadata API verwalten. Verschieben Sie Ihre letzten Metadaten-Informationen in ein exportiertes metadata-Objekt:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'React App',
  description: 'Web site created with Next.js.',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export const metadata = {
  title: 'React App',
  description: 'Web site created with Next.js.',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}

Mit den obigen Änderungen sind Sie von der Deklaration aller Inhalte in Ihrer index.html zur konventionsbasierten Vorgehensweise von Next.js übergegangen, die in das Framework integriert ist (Metadata API). Dieser Ansatz ermöglicht es Ihnen, Ihr SEO und die Web-Sharability Ihrer Seiten einfacher zu verbessern.

Schritt 6: Styles

Ähnlich wie Create React App hat Next.js integrierte Unterstützung für CSS Modules.

Wenn Sie eine globale CSS-Datei verwenden, importieren Sie diese in Ihre app/layout.tsx-Datei:

import '../index.css'

// ...

Wenn Sie Tailwind verwenden, müssen Sie postcss und autoprefixer installieren:

Terminal
npm install postcss autoprefixer

Erstellen Sie dann eine postcss.config.js-Datei im Stammverzeichnis Ihres Projekts:

postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

Schritt 7: Einstiegspunkt-Seite erstellen

In Next.js deklarieren Sie einen Einstiegspunkt für Ihre Anwendung, indem Sie eine page.tsx-Datei erstellen. Die nächste Entsprechung dieser Datei in CRA ist Ihre src/index.tsx-Datei. In diesem Schritt richten Sie den Einstiegspunkt Ihrer Anwendung ein.

Erstellen Sie ein [[...slug]]-Verzeichnis in Ihrem app-Verzeichnis.

Da diese Anleitung zunächst darauf abzielt, unsere Next.js-Anwendung als SPA (Single Page Application) einzurichten, benötigen Sie Ihren Seiten-Einstiegspunkt, um alle möglichen Routen Ihrer Anwendung abzufangen. Erstellen Sie dazu ein neues [[...slug]]-Verzeichnis in Ihrem app-Verzeichnis.

Dieses Verzeichnis wird als optionaler Catch-All-Route-Segment bezeichnet. Next.js verwendet einen dateisystembasierten Router, bei dem Verzeichnisse verwendet werden, um Routen zu definieren. Dieses spezielle Verzeichnis stellt sicher, dass alle Routen Ihrer Anwendung zu der darin enthaltenen page.tsx-Datei geleitet werden.

Erstellen Sie eine neue page.tsx-Datei im app/[[...slug]]-Verzeichnis mit folgendem Inhalt:

import '../../index.css'

export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return '...' // Wir werden dies aktualisieren
}
import '../../index.css'

export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return '...' // Wir werden dies aktualisieren
}

Diese Datei ist eine Server Component. Wenn Sie next build ausführen, wird die Datei in ein statisches Asset vorgerendert. Sie benötigt 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 CRA-Anwendung, die nur clientseitig ausgeführt wird.

'use client'

import React from 'react'
import dynamic from 'next/dynamic'

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

export function ClientOnly() {
  return <App />
}
'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-Komponenten werden dennoch auf dem Server zu HTML vorgerendert, bevor sie an den Client gesendet werden.

Da wir zunächst eine rein clientseitige Anwendung wünschen, können wir Next.js so konfigurieren, dass das Vorrendering ab der App-Komponente deaktiviert wird.

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

Aktualisieren Sie nun Ihren Einstiegspunkt-Seite, um die neue Komponente zu verwenden:

import '../../index.css'
import { ClientOnly } from './client'

export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return <ClientOnly />
}
import '../../index.css'
import { ClientOnly } from './client'

export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return <ClientOnly />
}

Schritt 8: Statische Bildimporte aktualisieren

Next.js behandelt statische Bildimporte etwas anders als CRA. Mit CRA gibt der Import einer Bilddatei deren öffentliche URL als String zurück:

App.tsx
import image from './img.png'

export default function App() {
  return <img src={image} />
}

Mit Next.js geben statische Bildimporte ein Objekt zurück. Dieses Objekt kann dann direkt mit der Next.js <Image>-Komponente verwendet werden, oder Sie können die src-Eigenschaft des Objekts mit Ihrem bestehenden <img>-Tag nutzen.

Die <Image>-Komponente bietet zusätzliche Vorteile wie automatische Bildoptimierung. Die <Image>-Komponente setzt automatisch die Attribute width und height des resultierenden <img> basierend auf den Bildabmessungen. Dies verhindert Layoutverschiebungen beim Laden des Bildes. Allerdings kann dies Probleme verursachen, wenn Ihre 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 zu verzerrten Bildern führen kann.

Die Beibehaltung des <img>-Tags reduziert die Anzahl der Änderungen in Ihrer Anwendung und verhindert die oben genannten Probleme. Sie können später optional zur <Image>-Komponente migrieren, um die Vorteile der Bildoptimierung durch Konfiguration eines Loaders zu nutzen oder zum standardmäßigen Next.js-Server zu wechseln, der automatische Bildoptimierung bietet.

Konvertieren Sie absolute Importpfade für Bilder aus /public in relative Importe:

// Vorher
import logo from '/logo.png'

// Nachher
import logo from '../public/logo.png'

Übergeben Sie die src-Eigenschaft des Bildes anstelle des gesamten Bildobjekts an Ihr <img>-Tag:

// Vorher
<img src={logo} />

// Nachher
<img src={logo.src} />

Alternativ können Sie die öffentliche URL für die Bilddatei basierend auf dem Dateinamen referenzieren. Beispielsweise wird public/logo.png das Bild unter /logo.png für Ihre Anwendung bereitstellen, was der src-Wert wäre.

Warnung: Wenn Sie TypeScript verwenden, könnten Sie Typfehler beim Zugriff auf die src-Eigenschaft erhalten. Sie können diese vorerst sicher ignorieren. Sie werden bis zum Ende dieser Anleitung behoben sein.

Schritt 9: Umgebungsvariablen migrieren

Next.js unterstützt .env-Umgebungsvariablen ähnlich wie CRA.

Der Hauptunterschied ist das Präfix, das verwendet wird, um Umgebungsvariablen auf der Client-Seite verfügbar zu machen. Ändern Sie alle Umgebungsvariablen mit dem Präfix REACT_APP_ zu NEXT_PUBLIC_.

Schritt 10: Skripte in package.json aktualisieren

Sie sollten nun in der Lage sein, Ihre Anwendung auszuführen, um zu testen, ob die Migration zu Next.js erfolgreich war. Zuvor müssen Sie jedoch Ihre scripts in der package.json mit Next.js-bezogenen Befehlen aktualisieren und .next, next-env.d.ts sowie dist zu Ihrer .gitignore-Datei hinzufügen:

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

Führen Sie nun npm run dev aus und öffnen Sie http://localhost:3000. Sie sollten Ihre Anwendung nun unter Next.js laufen sehen.

Schritt 11: Bereinigung

Sie können nun Ihren Codebase von Create React App bezogenen Artefakten bereinigen:

  • Löschen Sie src/index.tsx
  • Löschen Sie public/index.html
  • Entfernen Sie das reportWebVitals-Setup
  • Deinstallieren Sie CRA-Abhängigkeiten (react-scripts)

Bundler-Kompatibilität

Create React App und Next.js verwenden standardmäßig webpack für das Bundling.

Bei der Migration Ihrer CRA-Anwendung zu Next.js haben Sie möglicherweise eine benutzerdefinierte webpack-Konfiguration, die Sie migrieren möchten. Next.js unterstützt die Bereitstellung einer benutzerdefinierten webpack-Konfiguration.

Darüber hinaus unterstützt Next.js Turbopack über next dev --turbo, um die lokale Entwicklungsleistung zu verbessern. Turbopack unterstützt auch einige webpack-Loader für Kompatibilität und schrittweise Einführung.

Nächste Schritte

Wenn alles nach Plan verlaufen ist, haben Sie nun eine funktionierende Next.js-Anwendung, die als Single-Page-Anwendung läuft. Allerdings nutzen Sie noch nicht die meisten Vorteile von Next.js, aber Sie können nun schrittweise Änderungen vornehmen, um alle Vorteile zu nutzen. Hier ist, was Sie als Nächstes tun könnten:

Gut zu wissen: Die Verwendung eines statischen Exports unterstützt derzeit nicht die Verwendung des useParams-Hooks.