Migration von Next.js

Diese Anleitung bietet einen Schritt-für-Schritt-Prozess zur Migration eines Projekts vom Next.js App Router zu TanStack Start. Wir respektieren die leistungsstarken Funktionen von Next.js und möchten diesen Übergang so reibungslos wie möglich gestalten.

Schritt für Schritt (Grundlagen)

Diese Schritt-für-Schritt-Anleitung gibt einen Überblick darüber, wie Sie Ihr Next.js App Router-Projekt zu TanStack Start migrieren können. Das Ziel ist es, Ihnen die grundlegenden Schritte des Migrationsprozesses zu vermitteln, damit Sie diese an Ihre spezifischen Projektanforderungen anpassen können.

Voraussetzungen

Bevor wir beginnen, geht diese Anleitung davon aus, dass Ihre Projektstruktur wie folgt aussieht

txt
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── public
│   ├── file.svg
│   ├── globe.svg
│   ├── next.svg
│   ├── vercel.svg
│   └── window.svg
├── README.md
├── src
│   └── app
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.tsx
│       └── page.tsx
└── tsconfig.json
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── public
│   ├── file.svg
│   ├── globe.svg
│   ├── next.svg
│   ├── vercel.svg
│   └── window.svg
├── README.md
├── src
│   └── app
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.tsx
│       └── page.tsx
└── tsconfig.json

Alternativ können Sie mit dem Klonen der folgenden Starter-Vorlage mitarbeiten

sh
npx gitpick nrjdalal/awesome-templates/tree/main/next.js-apps/next.js-start next.js-start-er
npx gitpick nrjdalal/awesome-templates/tree/main/next.js-apps/next.js-start next.js-start-er

Diese Struktur ist eine grundlegende Next.js-Anwendung, die den App Router verwendet, den wir zu TanStack Start migrieren werden.

1. Next.js entfernen

Deinstallieren Sie zunächst Next.js und entfernen Sie zugehörige Konfigurationsdateien

sh
npm uninstall @tailwindcss/postcss next
rm postcss.config.* next.config.*
npm uninstall @tailwindcss/postcss next
rm postcss.config.* next.config.*

2. Erforderliche Abhängigkeiten installieren

TanStack Start nutzt Vite und TanStack Router

sh
npm i @tanstack/react-router @tanstack/react-start vite
npm i @tanstack/react-router @tanstack/react-start vite

Für Tailwind CSS und die Auflösung von Importen mithilfe von Pfad-Aliassen

sh
npm i -D @tailwindcss/vite tailwindcss vite-tsconfig-paths
npm i -D @tailwindcss/vite tailwindcss vite-tsconfig-paths

3. Projektkonfiguration aktualisieren

Nachdem Sie die notwendigen Abhängigkeiten installiert haben, aktualisieren Sie Ihre Projektkonfigurationsdateien, damit sie mit TanStack Start funktionieren.

  • package.json
json
{
  "type": "module",
  "scripts": {
    "dev": "vite dev",
    "build": "vite build",
    "start": "node .output/server/index.mjs"
  }
}
{
  "type": "module",
  "scripts": {
    "dev": "vite dev",
    "build": "vite build",
    "start": "node .output/server/index.mjs"
  }
}
  • vite.config.ts
ts
// vite.config.ts
import tailwindcss from '@tailwindcss/vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { defineConfig } from 'vite'
import tsconfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
  server: {
    port: 3000,
  },
  plugins: [
    tailwindcss(),
    // Enables Vite to resolve imports using path aliases.
    tsconfigPaths(),
    tanstackStart({
      tsr: {
        // Specifies the directory TanStack Router uses for your routes.
        routesDirectory: 'src/app', // Defaults to "src/routes"
      },
    }),
  ],
})
// vite.config.ts
import tailwindcss from '@tailwindcss/vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { defineConfig } from 'vite'
import tsconfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
  server: {
    port: 3000,
  },
  plugins: [
    tailwindcss(),
    // Enables Vite to resolve imports using path aliases.
    tsconfigPaths(),
    tanstackStart({
      tsr: {
        // Specifies the directory TanStack Router uses for your routes.
        routesDirectory: 'src/app', // Defaults to "src/routes"
      },
    }),
  ],
})

Standardmäßig ist routesDirectory auf src/routes gesetzt. Um die Konsistenz mit den Konventionen des Next.js App Routers beizubehalten, können Sie es stattdessen auf src/app setzen.

4. Root-Layout anpassen

TanStack Start verwendet einen Routing-Ansatz, der Remix ähnelt, mit einigen Änderungen zur Unterstützung verschachtelter Strukturen und spezieller Funktionen mithilfe von Tokens. Erfahren Sie mehr darüber im Leitfaden Routing Concepts.

Erstellen Sie anstelle von layout.tsx eine Datei namens __root.tsx im Verzeichnis src/app. Diese Datei dient als Root-Layout für Ihre Anwendung.

  • src/app/layout.tsx zu src/app/__root.tsx
tsx
- import type { Metadata } from "next" // [!code --]
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from "@tanstack/react-router"
import "./globals.css"

- export const metadata: Metadata = { // [!code --]
-   title: "Create Next App", // [!code --]
-   description: "Generated by create next app", // [!code --]
- } // [!code --]
export const Route = createRootRoute({
  head: () => ({
    meta: [
      { charSet: "utf-8" },
      {
        name: "viewport",
        content: "width=device-width, initial-scale=1",
      },
      { title: "TanStack Start Starter" }
    ],
  }),
  component: RootLayout,
})

- export default function RootLayout({ // [!code --]
-   children, // [!code --]
- }: Readonly<{ // [!code --]
-   children: React.ReactNode // [!code --]
- }>) { // [!code --]
-   return ( // [!code --]
-     <html lang="en"> // [!code --]
-       <body>{children}</body> // [!code --]
-     </html> // [!code --]
-   ) // [!code --]
- } // [!code --]
function RootLayout() {
  return (
    <html lang="en">
      <head>
        <HeadContent />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  )
}
- import type { Metadata } from "next" // [!code --]
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from "@tanstack/react-router"
import "./globals.css"

- export const metadata: Metadata = { // [!code --]
-   title: "Create Next App", // [!code --]
-   description: "Generated by create next app", // [!code --]
- } // [!code --]
export const Route = createRootRoute({
  head: () => ({
    meta: [
      { charSet: "utf-8" },
      {
        name: "viewport",
        content: "width=device-width, initial-scale=1",
      },
      { title: "TanStack Start Starter" }
    ],
  }),
  component: RootLayout,
})

- export default function RootLayout({ // [!code --]
-   children, // [!code --]
- }: Readonly<{ // [!code --]
-   children: React.ReactNode // [!code --]
- }>) { // [!code --]
-   return ( // [!code --]
-     <html lang="en"> // [!code --]
-       <body>{children}</body> // [!code --]
-     </html> // [!code --]
-   ) // [!code --]
- } // [!code --]
function RootLayout() {
  return (
    <html lang="en">
      <head>
        <HeadContent />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  )
}

5. Startseite anpassen

Erstellen Sie anstelle von page.tsx eine index.tsx-Datei für die Route /.

  • src/app/page.tsx zu src/app/index.tsx
tsx
- export default function Home() { // [!code --]
+ export const Route = createFileRoute('/')({ // [!code ++]
+   component: Home, // [!code ++]
+ }) // [!code ++]

+ function Home() { // [!code ++]
  return (
    <main className="min-h-dvh w-screen flex items-center justify-center flex-col gap-y-4 p-4">
      <img
        className="max-w-sm w-full"
        src="https://raw.githubusercontent.com/tanstack/tanstack.com/main/src/images/splash-dark.png"
        alt="TanStack Logo"
      />
      <h1>
        <span className="line-through">Next.js</span> TanStack Start
      </h1>
      <a
        className="bg-foreground text-background rounded-full px-4 py-1 hover:opacity-90"
        href="https://tanstack.de/start/latest"
        target="_blank"
      >
        Docs
      </a>
    </main>
  )
}
- export default function Home() { // [!code --]
+ export const Route = createFileRoute('/')({ // [!code ++]
+   component: Home, // [!code ++]
+ }) // [!code ++]

+ function Home() { // [!code ++]
  return (
    <main className="min-h-dvh w-screen flex items-center justify-center flex-col gap-y-4 p-4">
      <img
        className="max-w-sm w-full"
        src="https://raw.githubusercontent.com/tanstack/tanstack.com/main/src/images/splash-dark.png"
        alt="TanStack Logo"
      />
      <h1>
        <span className="line-through">Next.js</span> TanStack Start
      </h1>
      <a
        className="bg-foreground text-background rounded-full px-4 py-1 hover:opacity-90"
        href="https://tanstack.de/start/latest"
        target="_blank"
      >
        Docs
      </a>
    </main>
  )
}

6. Haben wir schon migriert?

Bevor Sie den Entwicklungsserver ausführen können, müssen Sie eine Router-Datei erstellen, die das Verhalten von TanStack Router innerhalb von TanStack Start definiert.

  • src/router.tsx
tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function createRouter() {
  const router = createTanStackRouter({
    routeTree,
    scrollRestoration: true,
  })

  return router
}

declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof createRouter>
  }
}
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function createRouter() {
  const router = createTanStackRouter({
    routeTree,
    scrollRestoration: true,
  })

  return router
}

declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof createRouter>
  }
}

🧠 Hier können Sie alles konfigurieren, von der Standard-Vorabladefunktion bis hin zur Aktualität des Cachings.

Machen Sie sich keine Sorgen, wenn Sie zu diesem Zeitpunkt einige TypeScript-Fehler sehen; der nächste Schritt wird diese beheben.

7. Migration überprüfen

Entwicklungsserver ausführen

sh
npm run dev
npm run dev

Besuchen Sie dann https://:3000. Sie sollten die TanStack Start-Willkommensseite mit ihrem Logo und einem Dokumentationslink sehen.

Wenn Probleme auftreten, überprüfen Sie die obigen Schritte und stellen Sie sicher, dass Dateinamen und Pfade exakt übereinstimmen. Eine Referenzimplementierung finden Sie im Repository nach der Migration.

Nächste Schritte (Fortgeschritten)

Nachdem Sie die grundlegende Struktur Ihrer Next.js-Anwendung nach TanStack Start migriert haben, können Sie weitere fortgeschrittene Funktionen und Konzepte erkunden.

Routing-Konzepte

RoutenbeispielNext.jsTanStack Start
Root-Layoutsrc/app/layout.tsxsrc/app/__root.tsx
/ (Startseite)src/app/page.tsxsrc/app/index.tsx
/posts (Statische Route)src/app/posts/page.tsxsrc/app/posts.tsx
/posts/[slug] (Dynamisch)src/app/posts/[slug]/page.tsxsrc/app/posts/$slug.tsx
/posts/[...slug] (Catch-All)src/app/posts/[...slug]/page.tsxsrc/app/posts/$.tsx
/api/endpoint (API-Route)src/app/api/endpoint/route.tssrc/app/api/endpoint.ts

Erfahren Sie mehr über die Routing Concepts.

Dynamische und Catch-All-Routen

Das Abrufen dynamischer Routenparameter in TanStack Start ist unkompliziert.

tsx
- export default async function Page({ // [!code --]
-   params, // [!code --]
- }: { // [!code --]
-   params: Promise<{ slug: string }> // [!code --]
- }) { // [!code --]
+ export const Route = createFileRoute('/app/posts/$slug')({ // [!code ++]
+   component: Page, // [!code ++]
+ }) // [!code ++]

+ function Page() { // [!code ++]
-   const { slug } = await params // [!code --]
+   const { slug } = Route.useParams() // [!code ++]
  return <div>My Post: {slug}</div>
}
- export default async function Page({ // [!code --]
-   params, // [!code --]
- }: { // [!code --]
-   params: Promise<{ slug: string }> // [!code --]
- }) { // [!code --]
+ export const Route = createFileRoute('/app/posts/$slug')({ // [!code ++]
+   component: Page, // [!code ++]
+ }) // [!code ++]

+ function Page() { // [!code ++]
-   const { slug } = await params // [!code --]
+   const { slug } = Route.useParams() // [!code ++]
  return <div>My Post: {slug}</div>
}

Hinweis: Wenn Sie eine Catch-All-Route erstellt haben (wie src/app/posts/$.tsx), können Sie über const { _splat } = Route.useParams() auf die Parameter zugreifen.

Ähnlich können Sie mit const { page, filter, sort } = Route.useSearch() auf searchParams zugreifen.

Erfahren Sie mehr über die Dynamischen und Catch-All-Routen.

tsx
- import Link from "next/link" // [!code --]
+ import { Link } from "@tanstack/react-router" // [!code ++]

function Component() {
-   return <Link href="/dashboard">Dashboard</Link> // [!code --]
+   return <Link to="/dashboard">Dashboard</Link> // [!code ++]
}
- import Link from "next/link" // [!code --]
+ import { Link } from "@tanstack/react-router" // [!code ++]

function Component() {
-   return <Link href="/dashboard">Dashboard</Link> // [!code --]
+   return <Link to="/dashboard">Dashboard</Link> // [!code ++]
}

Erfahren Sie mehr über die Links.

Server Aktionen Funktionen

tsx
- 'use server' // [!code --]
+ import { createServerFn } from "@tanstack/react-start" // [!code ++]

- export const create = async () => { // [!code --]
+ export const create = createServerFn().handler(async () => { // [!code ++]
  return true
- } // [!code --]
+ }) // [!code ++]
- 'use server' // [!code --]
+ import { createServerFn } from "@tanstack/react-start" // [!code ++]

- export const create = async () => { // [!code --]
+ export const create = createServerFn().handler(async () => { // [!code ++]
  return true
- } // [!code --]
+ }) // [!code ++]

Erfahren Sie mehr über die Serverfunktionen.

Server-Routen Handler

ts
- export async function GET() { // [!code --]
+ export const ServerRoute = createServerFileRoute().methods({ // [!code ++]
+   GET: async () => { // [!code ++]
    return Response.json("Hello, World!")
  }
+ }) // [!code ++]
- export async function GET() { // [!code --]
+ export const ServerRoute = createServerFileRoute().methods({ // [!code ++]
+   GET: async () => { // [!code ++]
    return Response.json("Hello, World!")
  }
+ }) // [!code ++]

Erfahren Sie mehr über die Server-Routen.

Schriftarten

tsx
- import { Inter } from "next/font/google" // [!code --]

- const inter = Inter({ // [!code --]
-   subsets: ["latin"], // [!code --]
-   display: "swap", // [!code --]
- }) // [!code --]

- export default function Page() { // [!code --]
-   return <p className={inter.className}>Font Sans</p> // [!code --]
- } // [!code --]
- import { Inter } from "next/font/google" // [!code --]

- const inter = Inter({ // [!code --]
-   subsets: ["latin"], // [!code --]
-   display: "swap", // [!code --]
- }) // [!code --]

- export default function Page() { // [!code --]
-   return <p className={inter.className}>Font Sans</p> // [!code --]
- } // [!code --]

Verwenden Sie anstelle von next/font den CSS-basierten Ansatz von Tailwind CSS. Installieren Sie Schriftarten (z. B. von Fontsource)

sh
npm i -D @fontsource-variable/dm-sans @fontsource-variable/jetbrains-mono
npm i -D @fontsource-variable/dm-sans @fontsource-variable/jetbrains-mono

Fügen Sie Folgendes zu src/app/globals.css hinzu

CSS
@import 'tailwindcss';

@import '@fontsource-variable/dm-sans'; /* [!code ++] */
@import '@fontsource-variable/jetbrains-mono'; /* [!code ++] */

@theme inline {
  --font-sans: 'DM Sans Variable', sans-serif; /* [!code ++] */
  --font-mono: 'JetBrains Mono Variable', monospace; /* [!code ++] */
  /* ... */
}

/* ... */
@import 'tailwindcss';

@import '@fontsource-variable/dm-sans'; /* [!code ++] */
@import '@fontsource-variable/jetbrains-mono'; /* [!code ++] */

@theme inline {
  --font-sans: 'DM Sans Variable', sans-serif; /* [!code ++] */
  --font-mono: 'JetBrains Mono Variable', monospace; /* [!code ++] */
  /* ... */
}

/* ... */

Daten abrufen

tsx
- export default async function Page() { // [!code --]
+ export const Route = createFileRoute('/')({ // [!code ++]
+   component: Page, // [!code ++]
+   loader: async () => { // [!code ++]
+     const res = await fetch('https://api.vercel.app/blog') // [!code ++]
+     return res.json() // [!code ++]
+   }, // [!code ++]
+ }) // [!code ++]

+ function Page() { // [!code ++]
-   const data = await fetch('https://api.vercel.app/blog') // [!code --]
-   const posts = await data.json() // [!code --]
+   const posts = Route.useLoaderData() // [!code ++]

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
- export default async function Page() { // [!code --]
+ export const Route = createFileRoute('/')({ // [!code ++]
+   component: Page, // [!code ++]
+   loader: async () => { // [!code ++]
+     const res = await fetch('https://api.vercel.app/blog') // [!code ++]
+     return res.json() // [!code ++]
+   }, // [!code ++]
+ }) // [!code ++]

+ function Page() { // [!code ++]
-   const data = await fetch('https://api.vercel.app/blog') // [!code --]
-   const posts = await data.json() // [!code --]
+   const posts = Route.useLoaderData() // [!code ++]

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
Unsere Partner
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
Prisma
Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.

Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.