Selektives serverseitiges 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 in eine vollständig interaktive Anwendung "hydriert".

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

  • Wenn beforeLoad oder loader browserabhängige APIs erfordern (z. B. localStorage).
  • Wenn die Routenkomponente von browserabhängigen APIs abhängt (z. B. canvas).

Die Funktion "Selektives SSR" von TanStack Start ermöglicht Ihnen die Konfiguration

  • 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 Rendering von Routenkomponenten vollständig. Selektives SSR ermöglicht Ihnen die Konfiguration der serverseitigen Verarbeitung auf Basis einzelner Routen, 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 der Standardwert true. Sie können diesen Standardwert mit der Option defaultSsr in createRouter ändern.

tsx
// src/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/solid-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/solid-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 ausgeführt:

  • 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 client</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 client</div>,
})

ssr: false

Dies deaktiviert serverseitig

  • 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 Hybridoption 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.
  • Deaktivieren Sie 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 Eigenschaft ssr 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 Funktion ssr 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 Vereinigung ü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 Details des Fehlers. Andernfalls ist status success und value enthält die validierten Daten.

Vererbung

Zur Laufzeit erbt eine Kind-Route die Konfiguration des selektiven SSR ihrer Eltern. Zum Beispiel:

tsx
root { ssr: undefined }
  posts { ssr: false }
     $postId { ssr: true }
root { ssr: undefined }
  posts { ssr: false }
     $postId { ssr: true }
  • root hat standardmäßig ssr: true.
  • posts setzt explizit ssr: false, sodass weder beforeLoad noch loader auf dem Server ausgeführt werden und die Routenkomponente nicht auf dem Server gerendert wird.
  • $postId setzt ssr: true, erbt aber ssr: false von seinem Elternteil.

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 }
  • root hat standardmäßig ssr: true.
  • posts setzt ssr: 'data-only', sodass beforeLoad und loader auf dem Server ausgeführt werden, aber die Routenkomponente nicht auf dem Server gerendert wird.
  • $postId setzt ssr: true, erbt aber ssr: 'data-only' von seinem Elternteil.
  • details setzt ssr: false, sodass weder beforeLoad noch loader auf dem Server ausgeführt werden und die Routenkomponente nicht auf dem Server gerendert wird.

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 keines von beiden konfiguriert ist, wird kein Fallback gerendert.

Auf dem Client während der Hydration 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. Die <html>-Shell muss jedoch 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 type * as Solid from 'solid-js'

import {
  HeadContent,
  Outlet,
  Scripts,
  createRootRoute,
} from '@tanstack/solid-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: Solid.JSX.Element }) {
  return (
    <>
      <HeadContent />
      {children}
      <Scripts />
    </>
  )
}

function RootComponent() {
  return (
    <div>
      <h1>This component will be rendered on the client</h1>
      <Outlet />
    </div>
  )
}
import type * as Solid from 'solid-js'

import {
  HeadContent,
  Outlet,
  Scripts,
  createRootRoute,
} from '@tanstack/solid-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: Solid.JSX.Element }) {
  return (
    <>
      <HeadContent />
      {children}
      <Scripts />
    </>
  )
}

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.