在 TanStack Start 中,默认情况下,与初始请求匹配的路由会在服务器上进行渲染。这意味着 beforeLoad 和 loader 会在服务器上执行,然后渲染路由组件。生成的 HTML 将发送到客户端,客户端会将标记水合(hydrate)为完全交互式的应用程序。
但是,在某些情况下,您可能希望为某些路由或所有路由禁用 SSR,例如:
TanStack Start 的选择性 SSR 功能允许您配置:
TanStack Start 的 SPA 模式 会完全禁用 beforeLoad 和 loader 的服务器端执行,以及路由组件的服务器端渲染。选择性 SSR 允许您按路由配置服务器端处理,无论是静态的还是动态的。
您可以使用 ssr 属性来控制路由在初始服务器请求期间的处理方式。如果未设置此属性,则默认为 true。您可以使用 createRouter 中的 defaultSsr 选项更改此默认值。
// 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
}
这是默认行为,除非另有配置。在初始请求时,它将:
// 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>,
})
这会禁用服务器端:
// 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>,
})
这个混合选项将:
// 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>,
})
为了获得更大的灵活性,您可以使用 ssr 属性的函数式形式,在运行时决定是否对路由进行 SSR。
// 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>,
})
ssr 函数仅在初始请求期间在服务器上运行,并且会从客户端捆绑包中剥离。
search 和 params 会在验证后作为区分联合(discriminated union)传递。
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 }
如果验证失败,status 将是 error,error 将包含失败的详细信息。否则,status 将是 success,value 将包含已验证的数据。
在运行时,子路由将继承其父级的选择性 SSR 配置。例如:
root { ssr: undefined }
posts { ssr: false }
$postId { ssr: true }
root { ssr: undefined }
posts { ssr: false }
$postId { ssr: true }
另一个例子:
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 }
对于第一个设置为 ssr: false 或 ssr: 'data-only' 的路由,服务器将渲染路由的 pendingComponent 作为回退。如果未配置 pendingComponent,则将渲染 defaultPendingComponent。如果两者都未配置,则不会渲染任何回退。
在客户端水合(hydrate)期间,此回退将至少显示 minPendingMs(或如果未配置则为 defaultPendingMinMs),即使该路由没有定义 beforeLoad 或 loader。
您可以禁用根路由组件的服务器端渲染,但是 <html> 外壳仍然需要在服务器上渲染。这个外壳通过 shellComponent 属性进行配置,并接受一个名为 children 的属性。shellComponent 始终会进行 SSR,并分别包裹根 component、根 errorComponent 或根 notFound 组件。
一个根路由的最小设置,其中路由组件的 SSR 被禁用,如下所示:
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>
)
}
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。