Migration von Create React App zu Next.js

Diese Anleitung hilft Ihnen, eine bestehende Create React App (CRA)-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 initiale Ladezeit

Create React App verwendet rein client-seitiges React. Client-seitige Anwendungen, auch bekannt als Single-Page Applications (SPAs), haben oft eine langsame initiale Ladezeit. Dies liegt an 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.

Kein automatisches Code-Splitting

Das vorherige Problem der langsamen Ladezeiten kann teilweise durch Code-Splitting gemildert werden. Wenn Sie jedoch versuchen, Code-Splitting manuell durchzuführen, können Sie unbeabsichtigt Netzwerk-Wasserfälle verursachen. Next.js bietet automatisches Code-Splitting und Tree-Shaking, das in seinen Router und Build-Prozess 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 Muster für das Daten-Fetching in einer SPA ist das Rendern eines Platzhalters und anschließendes Abrufen von Daten, nachdem die Komponente gemountet wurde. Leider kann eine Kind-Komponente erst mit dem Datenabruf beginnen, nachdem ihre Eltern-Komponente ihre eigenen Daten geladen hat, was zu einem "Wasserfall" von Anfragen führt.

Während client-seitiges Daten-Fetching in Next.js unterstützt wird, ermöglicht Next.js Ihnen auch, das Daten-Fetching auf den Server zu verlagern. Dies eliminiert oft Client-Server-Wasserfälle vollständig.

Schnelle und gezielte Ladezustände

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

Dies ermöglicht Ihnen den Aufbau von Seiten, die schneller laden und Layout-Shifts vermeiden.

Wählen Sie die Datenabrufstrategie

Abhängig von Ihren Anforderungen ermöglicht Next.js Ihnen, Ihre Datenabrufstrategie auf Seiten- oder Komponentenebene zu wählen. Beispielsweise können Sie Daten von Ihrem CMS abrufen und Blog-Beiträge zur Build-Zeit (SSG) für schnelle Ladezeiten rendern oder bei Bedarf Daten zur Anfragezeit (SSR) abrufen.

Middleware

Next.js Middleware ermöglicht es Ihnen, Code auf dem Server auszuführen, bevor eine Anfrage abgeschlossen ist. Beispielsweise können Sie einen Flash nicht authentifizierter Inhalte vermeiden, indem Sie einen Benutzer in der Middleware für authentifizierungs-pflichtige Seiten auf eine Login-Seite umleiten. Sie können sie auch für Funktionen wie A/B-Tests, Experimente und Internationalisierung verwenden.

Integrierte Optimierungen

Bilder, Schriftarten und Skripte von Drittanbietern haben oft einen großen Einfluss auf die Performance einer Anwendung. Next.js enthält spezialisierte Komponenten und APIs, die diese automatisch für Sie optimieren.

Migrationsschritte

Unser Ziel ist es, so schnell wie möglich eine funktionierende Next.js-Anwendung zu erhalten, damit Sie dann Next.js-Funktionen schrittweise übernehmen können. Zunächst behandeln wir Ihre Anwendung als rein client-seitige Anwendung (SPA), ohne Ihren bestehenden Router sofort zu ersetzen. Dies reduziert Komplexität und Merge-Konflikte.

Hinweis: Wenn Sie erweiterte CRA-Konfigurationen wie ein benutzerdefiniertes homepage-Feld in Ihrer package.json, einen benutzerdefinierten Service Worker oder spezifische Babel/Webpack-Anpassungen verwenden, lesen Sie bitte den Abschnitt Zusätzliche Überlegungen am Ende dieser Anleitung für Tipps zur Replikation oder Anpassung dieser Funktionen in Next.js.

Schritt 1: Next.js-Abhängigkeit installieren

Installieren Sie Next.js in Ihrem bestehenden Projekt:

Terminal
npm install next@latest

Schritt 2: Next.js-Konfigurationsdatei erstellen

Erstellen Sie eine next.config.ts im Stammverzeichnis Ihres Projekts (auf derselben Ebene wie Ihre package.json). Diese Datei enthält Ihre Next.js-Konfigurationsoptionen.

next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  output: 'export', // Erzeugt eine Single-Page Application (SPA)
  distDir: 'build', // Ändert das Build-Ausgabeverzeichnis zu `build`
}

export default nextConfig

Hinweis: Die Verwendung von output: 'export' bedeutet, dass Sie einen statischen Export durchführen. Sie haben keinen Zugriff auf serverseitige Funktionen wie SSR oder APIs. Sie können diese Zeile entfernen, um Next.js-Serverfunktionen zu nutzen.

Schritt 3: Root-Layout erstellen

Eine Next.js-App Router-Anwendung muss eine Root-Layout-Datei enthalten, die eine React Server Component ist und alle Ihre Seiten umschließt.

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

  1. Erstellen Sie ein neues app-Verzeichnis in Ihrem src-Ordner (oder im Projektstamm, wenn Sie app im Stamm bevorzugen).
  2. Erstellen Sie im app-Verzeichnis eine layout.tsx (oder layout.js)-Datei:
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return '...'
}
export default function RootLayout({ children }) {
  return '...'
}

Kopieren Sie nun den Inhalt Ihrer alten index.html in diese <RootLayout>-Komponente. Ersetzen Sie body div#root (und body noscript) durch <div id="root">{children}</div>.

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: Next.js ignoriert standardmäßig CRA’s public/manifest.json, zusätzliche Icongrafik und Testkonfiguration. Wenn Sie diese benötigen, bietet Next.js Unterstützung mit seiner Metadata API und Test-Einrichtung.

Schritt 4: Metadaten

Next.js fügt automatisch die <meta charset="UTF-8" /> und <meta name="viewport" content="width=device-width, initial-scale=1" />-Tags ein, sodass Sie sie aus <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>
  )
}

Jede Metadatendatei wie favicon.ico, icon.png, robots.txt wird automatisch zum <head>-Tag der Anwendung hinzugefügt, solange Sie sie in der obersten Ebene des app-Verzeichnisses platziert haben. Nachdem Sie alle unterstützten Dateien in das app-Verzeichnis verschoben haben, können Sie ihre <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 Elemente in Ihrer index.html zu dem in das Framework integrierten, konventionsbasierten Ansatz von Next.js (Metadata API) gewechselt. Dieser Ansatz ermöglicht es Ihnen, Ihr SEO und die Web-Sharing-Fähigkeit Ihrer Seiten einfacher zu verbessern.

Schritt 5: Stile

Wie CRA unterstützt Next.js CSS Modules out of the box. Es unterstützt auch globale CSS-Imports.

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

import '../index.css'

export const 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>
  )
}

Wenn Sie Tailwind CSS verwenden, lesen Sie unsere Installationsdokumentation.

Schritt 6: Einstiegspunkt-Seite erstellen

Create React App verwendet src/index.tsx (oder index.js) als Einstiegspunkt. In Next.js (App Router) entspricht jeder Ordner innerhalb des app-Verzeichnisses einer Route, und jeder Ordner sollte eine page.tsx enthalten.

Da wir die Anwendung zunächst als SPA behalten und alle Routen abfangen möchten, verwenden wir eine optionale Catch-All-Route.

  1. Erstellen Sie ein [[...slug]]-Verzeichnis innerhalb von app.
app
 [[...slug]]
 page.tsx
 layout.tsx
  1. Fügen Sie Folgendes zu page.tsx hinzu:
export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return '...' // Wir werden dies aktualisieren
}
export function generateStaticParams() {
  return [{ slug: [''] }]
}

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

Dies teilt Next.js mit, eine einzelne Route für den leeren Slug (/) zu generieren, wodurch effektiv alle Routen derselben Seite zugeordnet werden. Diese Seite ist eine Server Component, die in statisches HTML vorgerendert wird.

Schritt 7: Client-Only-Einstiegspunkt hinzufügen

Als nächstes betten wir die Root-App-Komponente Ihrer CRA in eine Client Component ein, damit die gesamte Logik client-seitig bleibt. Wenn Sie Next.js zum ersten Mal verwenden, ist es wichtig zu wissen, dass Client-Komponenten (standardmäßig) trotzdem auf dem Server vorgerendert werden. Sie können sie sich als mit der zusätzlichen Fähigkeit vorstellen, client-seitiges JavaScript auszuführen.

Erstellen Sie eine client.tsx (oder client.js) in app/[[...slug]]/:

'use client'

import dynamic from 'next/dynamic'

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

export function ClientOnly() {
  return <App />
}
'use client'

import dynamic from 'next/dynamic'

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

export function ClientOnly() {
  return <App />
}
  • Die 'use client'-Direktive macht diese Datei zu einer Client Component.
  • Der dynamic-Import mit ssr: false deaktiviert das Server-Side-Rendering für die <App />-Komponente, wod sie wirklich client-only (SPA) wird.

Aktualisieren Sie nun Ihre page.tsx (oder page.js), um Ihre neue Komponente zu verwenden:

import { ClientOnly } from './client'

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

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

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

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

Schritt 8: Statische Bildimporte aktualisieren

In CRA gibt der Import einer Bilddatei deren öffentliche URL als Zeichenkette zurück:

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 width- und height-Attribute des resultierenden <img>-Tags basierend auf den Abmessungen des Bildes. Dies verhindert Layoutverschiebungen beim Laden des Bildes. Allerdings kann dies Probleme verursachen, wenn Ihre App Bilder enthält, bei denen nur eine ihrer Abmessungen gestaltet ist, ohne dass die andere auf auto gesetzt ist. Wenn nicht auf auto gesetzt, wird die Abmessung standardmäßig auf den Wert des <img>-Dimension-Attributs gesetzt, was zu einer verzerrten Darstellung des Bildes 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.

Absolute Importpfade für Bilder aus /public in relative Importe umwandeln:

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

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

Die src-Eigenschaft des Bildes anstelle des gesamten Bildobjekts an Ihr <img>-Tag übergeben:

// 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 dann der src-Wert wäre.

Warnung: Wenn Sie TypeScript verwenden, könnten Sie Typfehler beim Zugriff auf die src-Eigenschaft erhalten. Um diese zu beheben, müssen Sie next-env.d.ts zum include-Array Ihrer tsconfig.json-Datei hinzufügen. Next.js generiert diese Datei automatisch, wenn Sie Ihre Anwendung in Schritt 9 ausführen.

Schritt 9: Umgebungsvariablen migrieren

Next.js unterstützt Umgebungsvariablen ähnlich wie CRA, erfordert jedoch das Präfix NEXT_PUBLIC_ für alle Variablen, die im Browser verfügbar sein sollen.

Der Hauptunterschied liegt im Präfix, das verwendet wird, um Umgebungsvariablen clientseitig verfügbar zu machen. Ändern Sie alle Umgebungsvariablen mit dem Präfix REACT_APP_ zu NEXT_PUBLIC_.

Schritt 10: Skripte in package.json aktualisieren

Aktualisieren Sie Ihre package.json-Skripte, um Next.js-Befehle zu verwenden. Fügen Sie außerdem .next und next-env.d.ts zu Ihrer .gitignore hinzu:

package.json
{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "npx serve@latest ./build"
  }
}
.gitignore
# ...
.next
next-env.d.ts

Nun können Sie ausführen:

npm run dev

Öffnen Sie http://localhost:3000. Sie sollten Ihre Anwendung nun mit Next.js (im SPA-Modus) laufen sehen.

Schritt 11: Bereinigung

Sie können jetzt Artefakte entfernen, die spezifisch für Create React App sind:

  • public/index.html
  • src/index.tsx
  • src/react-app-env.d.ts
  • Das reportWebVitals-Setup
  • Die react-scripts-Abhängigkeit (deinstallieren Sie sie aus package.json)

Zusätzliche Überlegungen

Verwendung eines benutzerdefinierten homepage in CRA

Falls Sie das Feld homepage in Ihrer CRA-package.json verwendet haben, um die App unter einem bestimmten Unterpfad zu servieren, können Sie dies in Next.js mit der basePath-Konfiguration in next.config.ts replizieren:

next.config.ts
import { NextConfig } from 'next'

const nextConfig: NextConfig = {
  basePath: '/my-subpath',
  // ...
}

export default nextConfig

Umgang mit einem benutzerdefinierten Service Worker

Falls Sie den Service Worker von CRA (z.B. serviceWorker.js aus create-react-app) verwendet haben, können Sie lernen, wie Sie Progressive Web Applications (PWAs) mit Next.js erstellen.

Proxying von API-Anfragen

Falls Ihre CRA-App das Feld proxy in package.json verwendet hat, um Anfragen an einen Backend-Server weiterzuleiten, können Sie dies mit Next.js Rewrites in next.config.ts replizieren:

next.config.ts
import { NextConfig } from 'next'

const nextConfig: NextConfig = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'https://your-backend.com/:path*',
      },
    ]
  },
}

Benutzerdefinierte Webpack-/Babel-Konfiguration

Falls Sie eine benutzerdefinierte Webpack- oder Babel-Konfiguration in CRA hatten, können Sie die Next.js-Konfiguration in next.config.ts erweitern:

next.config.ts
import { NextConfig } from 'next'

const nextConfig: NextConfig = {
  webpack: (config, { isServer }) => {
    // Webpack-Konfiguration hier anpassen
    return config
  },
}

export default nextConfig

Hinweis: Dies erfordert das Deaktivieren von Turbopack durch Entfernen von --turbopack aus Ihrem dev-Skript.

TypeScript-Einrichtung

Next.js richtet TypeScript automatisch ein, wenn eine tsconfig.json vorhanden ist. Stellen Sie sicher, dass next-env.d.ts in Ihrem tsconfig.json-include-Array aufgeführt ist:

{
  "include": ["next-env.d.ts", "app/**/*", "src/**/*"]
}

Bundler-Kompatibilität

Sowohl Create React App als auch Next.js verwenden standardmäßig webpack für das Bundling. Next.js bietet zusätzlich Turbopack für schnellere lokale Entwicklung mit:

next dev --turbopack

Sie können weiterhin eine benutzerdefinierte Webpack-Konfiguration bereitstellen, falls Sie erweiterte Webpack-Einstellungen von CRA migrieren müssen.

Nächste Schritte

Falls alles funktioniert, haben Sie nun eine funktionierende Next.js-Anwendung, die als Single-Page-Anwendung läuft. Sie nutzen noch keine Next.js-Funktionen wie Server-seitiges Rendering oder dateibasiertes Routing, können dies jedoch nun schrittweise tun:

Hinweis: Die Verwendung eines statischen Exports (output: 'export') unterstützt derzeit nicht den useParams-Hook oder andere Server-Funktionen. Um alle Next.js-Funktionen nutzen zu können, entfernen Sie output: 'export' aus Ihrer next.config.ts.