Selektives Server-Side Rendering (SSR)

Was ist selektives SSR?

In TanStack Start werden Routen, die der anfänglichen Anfrage entsprechen, standardmäßig auf dem Server gerendert. Das bedeutet, dass beforeLoad und loader auf dem Server ausgeführt werden, gefolgt vom Rendern der Routenkomponenten. Der resultierende HTML wird an den Client gesendet, der das Markup zu einer voll interaktiven Anwendung hydriert.

Es gibt jedoch Fälle, in denen Sie SSR für bestimmte Routen oder alle Routen deaktivieren möchten, z. B.

  • Wenn beforeLoad oder loader Browser-spezifische APIs benötigen (z. B. localStorage).
  • Wenn die Routenkomponente von Browser-spezifischen APIs abhängt (z. B. canvas).

Die selektive SSR-Funktion von TanStack Start ermöglicht es Ihnen, zu konfigurieren

  • Welche Routen beforeLoad oder loader auf dem Server ausführen sollen.
  • Welche Routenkomponenten auf dem Server gerendert werden sollen.

Wie verhält sich das im Vergleich zum SPA-Modus?

Der SPA-Modus von TanStack Start deaktiviert die serverseitige Ausführung von beforeLoad und loader sowie das serverseitige Rendern von Routenkomponenten vollständig. Selektives SSR ermöglicht es Ihnen, die serverseitige Verarbeitung pro Route zu konfigurieren, entweder statisch oder dynamisch.

Konfiguration

Sie können steuern, wie eine Route während der anfänglichen Serveranfrage behandelt wird, indem Sie die Eigenschaft ssr verwenden. Wenn diese Eigenschaft nicht gesetzt ist, ist ihr Standardwert true. Sie können diesen Standardwert mit der Option defaultSsr in createRouter ändern.

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

export function createRouter() {
  const router = createTanStackRouter({
    routeTree,
    scrollRestoration: true,
    defaultPendingComponent: () => <div>Loading...</div>,
    // Disable SSR by default
    defaultSsr: false,
  })

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

export function createRouter() {
  const router = createTanStackRouter({
    routeTree,
    scrollRestoration: true,
    defaultPendingComponent: () => <div>Loading...</div>,
    // Disable SSR by default
    defaultSsr: false,
  })

  return router
}

ssr: true

Dies ist das Standardverhalten, sofern nicht anders konfiguriert. Bei der anfänglichen Anfrage wird Folgendes geschehen:

  • Führen Sie beforeLoad auf dem Server aus und senden Sie den resultierenden Kontext an den Client.
  • Führen Sie loader auf dem Server aus und senden Sie die Loader-Daten an den Client.
  • Rendern Sie die Komponente auf dem Server und senden Sie das HTML-Markup an den Client.
tsx
// src/routes/posts/$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  ssr: true,
  beforeLoad: () => {
    console.log('Executes on the server during the initial request')
    console.log('Executes on the client for subsequent navigation')
  },
  loader: () => {
    console.log('Executes on the server during the initial request')
    console.log('Executes on the client for subsequent navigation')
  },
  component: () => <div>This component is rendered on the server</div>,
})
// src/routes/posts/$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  ssr: true,
  beforeLoad: () => {
    console.log('Executes on the server during the initial request')
    console.log('Executes on the client for subsequent navigation')
  },
  loader: () => {
    console.log('Executes on the server during the initial request')
    console.log('Executes on the client for subsequent navigation')
  },
  component: () => <div>This component is rendered on the server</div>,
})

ssr: false

Dies deaktiviert serverseitige

  • Ausführung von beforeLoad und loader der Route.
  • Rendering der Routenkomponente.
tsx
// src/routes/posts/$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  ssr: false,
  beforeLoad: () => {
    console.log('Executes on the client during hydration')
  },
  loader: () => {
    console.log('Executes on the client during hydration')
  },
  component: () => <div>This component is rendered on the client</div>,
})
// src/routes/posts/$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  ssr: false,
  beforeLoad: () => {
    console.log('Executes on the client during hydration')
  },
  loader: () => {
    console.log('Executes on the client during hydration')
  },
  component: () => <div>This component is rendered on the client</div>,
})

ssr: 'data-only'

Diese hybride Option wird

  • Führen Sie beforeLoad auf dem Server aus und senden Sie den resultierenden Kontext an den Client.
  • Führen Sie loader auf dem Server aus und senden Sie die Loader-Daten an den Client.
  • Deaktiviert das serverseitige Rendering der Routenkomponente.
tsx
// src/routes/posts/$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  ssr: 'data-only',
  beforeLoad: () => {
    console.log('Executes on the server during the initial request')
    console.log('Executes on the client for subsequent navigation')
  },
  loader: () => {
    console.log('Executes on the server during the initial request')
    console.log('Executes on the client for subsequent navigation')
  },
  component: () => <div>This component is rendered on the client</div>,
})
// src/routes/posts/$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  ssr: 'data-only',
  beforeLoad: () => {
    console.log('Executes on the server during the initial request')
    console.log('Executes on the client for subsequent navigation')
  },
  loader: () => {
    console.log('Executes on the server during the initial request')
    console.log('Executes on the client for subsequent navigation')
  },
  component: () => <div>This component is rendered on the client</div>,
})

Funktionale Form

Für mehr Flexibilität können Sie die funktionale Form der ssr-Eigenschaft verwenden, um zur Laufzeit zu entscheiden, ob eine Route SSR-gerendert werden soll.

tsx
// src/routes/docs/$docType/$docId.tsx
export const Route = createFileRoute('/docs/$docType/$docId')({
  validateSearch: z.object({ details: z.boolean().optional() }),
  ssr: ({ params, search }) => {
    if (params.status === 'success' && params.value.docType === 'sheet') {
      return false
    }
    if (search.status === 'success' && search.value.details) {
      return 'data-only'
    }
  },
  beforeLoad: () => {
    console.log('Executes on the server depending on the result of ssr()')
  },
  loader: () => {
    console.log('Executes on the server depending on the result of ssr()')
  },
  component: () => <div>This component is rendered on the client</div>,
})
// src/routes/docs/$docType/$docId.tsx
export const Route = createFileRoute('/docs/$docType/$docId')({
  validateSearch: z.object({ details: z.boolean().optional() }),
  ssr: ({ params, search }) => {
    if (params.status === 'success' && params.value.docType === 'sheet') {
      return false
    }
    if (search.status === 'success' && search.value.details) {
      return 'data-only'
    }
  },
  beforeLoad: () => {
    console.log('Executes on the server depending on the result of ssr()')
  },
  loader: () => {
    console.log('Executes on the server depending on the result of ssr()')
  },
  component: () => <div>This component is rendered on the client</div>,
})

Die ssr-Funktion wird nur auf dem Server während der anfänglichen Anfrage ausgeführt und aus dem Client-Bundle entfernt.

search und params werden nach der Validierung als diskriminierte Union übergeben.

tsx
params:
    | { status: 'success'; value: Expand<ResolveAllParamsFromParent<TParentRoute, TParams>> }
    | { status: 'error'; error: unknown }
search:
    | { status: 'success'; value: Expand<ResolveFullSearchSchema<TParentRoute, TSearchValidator>> }
    | { status: 'error'; error: unknown }
params:
    | { status: 'success'; value: Expand<ResolveAllParamsFromParent<TParentRoute, TParams>> }
    | { status: 'error'; error: unknown }
search:
    | { status: 'success'; value: Expand<ResolveFullSearchSchema<TParentRoute, TSearchValidator>> }
    | { status: 'error'; error: unknown }

Wenn die Validierung fehlschlägt, ist status error und error enthält die Fehlerdetails. Andernfalls ist status success und value enthält die validierten Daten.

Vererbung

Zur Laufzeit erbt eine untergeordnete Route die Konfiguration des selektiven SSR ihrer Elternroute. Der geerbte Wert kann jedoch nur restriktiver geändert werden (d. h. von true zu data-only oder von data-only zu false). Zum Beispiel:

tsx
root { ssr: undefined }
  posts { ssr: false }
     $postId { ssr: true }
root { ssr: undefined }
  posts { ssr: false }
     $postId { ssr: true }
  • Die root-Route hat standardmäßig ssr: true.
  • Die posts-Route setzt explizit ssr: false, sodass weder beforeLoad noch loader auf dem Server ausgeführt werden und die Routenkomponente nicht auf dem Server gerendert wird.
  • Die $postId-Route setzt ssr: true, erbt aber ssr: false von ihrer übergeordneten Route. Da der geerbte Wert nur restriktiver geändert werden kann, hat ssr: true keine Auswirkung und das geerbte ssr: false bleibt bestehen.

Ein weiteres Beispiel

tsx
root { ssr: undefined }
  posts { ssr: 'data-only' }
     $postId { ssr: true }
       details { ssr: false }
root { ssr: undefined }
  posts { ssr: 'data-only' }
     $postId { ssr: true }
       details { ssr: false }
  • Die root-Route hat standardmäßig ssr: true.
  • Die posts-Route setzt ssr: 'data-only', sodass beforeLoad und loader auf dem Server ausgeführt werden, die Routenkomponente jedoch nicht.
  • Die $postId-Route setzt ssr: true, erbt aber ssr: 'data-only' von ihrer übergeordneten Route.
  • Die details-Route setzt ssr: false, sodass weder beforeLoad noch loader auf dem Server ausgeführt werden und die Routenkomponente nicht auf dem Server gerendert wird. Hier wird der geerbte Wert restriktiver geändert, daher überschreibt ssr: false den geerbten Wert.

Fallback-Rendering

Für die erste Route mit ssr: false oder ssr: 'data-only' rendert der Server die pendingComponent der Route als Fallback. Wenn pendingComponent nicht konfiguriert ist, wird die defaultPendingComponent gerendert. Wenn beides nicht konfiguriert ist, wird kein Fallback gerendert.

Auf dem Client während der Hydrierung wird dieser Fallback für mindestens minPendingMs (oder defaultPendingMinMs, wenn nicht konfiguriert) angezeigt, auch wenn die Route keine beforeLoad oder loader definiert hat.

Wie deaktiviere ich SSR für die Root-Route?

Sie können das serverseitige Rendering der Root-Routenkomponente deaktivieren, jedoch muss die <html>-Shell weiterhin auf dem Server gerendert werden. Diese Shell wird über die Eigenschaft shellComponent konfiguriert und nimmt eine einzelne Eigenschaft children entgegen. Die shellComponent wird immer SSR-gerendert und umschließt die Root- component, die Root- errorComponent oder die Root- notFound Komponente entsprechend.

Ein minimales Setup einer Root-Route mit deaktiviertem SSR für die Routenkomponente sieht wie folgt aus:

tsx
import * as React from 'react'

import {
  HeadContent,
  Outlet,
  Scripts,
  createRootRoute,
} from '@tanstack/react-router'

export const Route = createRootRoute({
  shellComponent: RootShell,
  component: RootComponent,
  errorComponent: () => <div>Error</div>,
  notFoundComponent: () => <div>Not found</>,
  ssr: false // or `defaultSsr: false` on the router
})

function RootShell({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <head>
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  )
}

function RootComponent() {
  return (
    <div>
      <h1>This component will be rendered on the client</h1>
      <Outlet />
    </div>
  )
}
import * as React from 'react'

import {
  HeadContent,
  Outlet,
  Scripts,
  createRootRoute,
} from '@tanstack/react-router'

export const Route = createRootRoute({
  shellComponent: RootShell,
  component: RootComponent,
  errorComponent: () => <div>Error</div>,
  notFoundComponent: () => <div>Not found</>,
  ssr: false // or `defaultSsr: false` on the router
})

function RootShell({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <head>
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  )
}

function RootComponent() {
  return (
    <div>
      <h1>This component will be rendered on the client</h1>
      <Outlet />
    </div>
  )
}
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.