对于那些不需要 SSR 来满足 SEO、爬虫或性能要求的应用程序,可以将包含应用程序“Shell”(甚至特定路由的预渲染 HTML)的静态 HTML 文件发送给用户,这些文件包含引导应用程序在客户端运行所需的 html、head 和 body 标签。
不使用 SSR 并不意味着放弃服务器端功能! SPA 模式实际上与 Server Functions、Server Routes 或其他外部 API 等服务器端功能搭配得非常好。它仅仅意味着初始文档在客户端使用 JavaScript 渲染之前,不会包含应用程序的完整渲染 HTML。
启用 SPA 模式后,运行 Start 构建会有一个额外的预渲染步骤来生成 Shell。这是通过以下方式完成的:
注意
其他路由也可以被预渲染,并且在 SPA 模式下建议尽可能多地预渲染,但这并非 SPA 模式正常工作的必要条件。
要配置 SPA 模式,可以在 Start 插件的选项中添加一些配置项。
// vite.config.ts
export default defineConfig({
plugins: [
TanStackStart({
spa: {
enabled: true,
},
}),
],
})
// vite.config.ts
export default defineConfig({
plugins: [
TanStackStart({
spa: {
enabled: true,
},
}),
],
})
将纯粹的客户端 SPA 部署到主机或 CDN 通常需要使用重定向来确保 URL 被正确地重写到 SPA Shell。任何部署都应按此顺序包含这些优先级:
我们使用 Netlify 的 _redirects 文件将所有 404 请求重写到 SPA Shell。
# Catch all other 404 requests and rewrite them to the SPA shell
/* /_shell.html 200
# Catch all other 404 requests and rewrite them to the SPA shell
/* /_shell.html 200
同样,使用 Netlify 的 _redirects 文件,我们可以允许特定子路径通过到服务器。
# Allow requests to /_serverFn/* to be routed through to the server (If you have configured your server function base path to be something other than /_serverFn, use that instead)
/_serverFn/* /_serverFn/:splat 200
# Allow any requests to /api/* to be routed through to the server (Server routes can be created at any path, so you must ensure that any server routes you want to use are under this path, or simply add additional redirects for each server route base you want to expose)
/api/* /api/:splat 200
# Catch all other 404 requests and rewrite them to the SPA shell
/* /_shell.html 200
# Allow requests to /_serverFn/* to be routed through to the server (If you have configured your server function base path to be something other than /_serverFn, use that instead)
/_serverFn/* /_serverFn/:splat 200
# Allow any requests to /api/* to be routed through to the server (Server routes can be created at any path, so you must ensure that any server routes you want to use are under this path, or simply add additional redirects for each server route base you want to expose)
/api/* /api/:splat 200
# Catch all other 404 requests and rewrite them to the SPA shell
/* /_shell.html 200
用于生成 SPA Shell 的默认路径名是 /。我们称之为Shell 掩码路径。由于不会包含匹配的路由,因此用于生成 Shell 的路径名大部分情况下无关紧要,但仍然可以配置。
注意
建议将 Shell 掩码路径的默认值 / 保留。
// vite.config.ts
export default defineConfig({
plugins: [
tanstackStart({
spa: {
maskPath: '/app',
},
}),
],
})
// vite.config.ts
export default defineConfig({
plugins: [
tanstackStart({
spa: {
maskPath: '/app',
},
}),
],
})
prerender 选项用于配置 SPA Shell 的预渲染行为,并接受与预渲染指南中相同的 prerender 选项。
默认情况下,将设置以下 prerender 选项:
这意味着,默认情况下,Shell 不会被爬取以查找链接进行进一步预渲染,并且不会重试预渲染失败。
您可以通过提供自己的 prerender 选项来覆盖这些设置。
// vite.config.ts
export default defineConfig({
plugins: [
TanStackStart({
spa: {
prerender: {
outputPath: '/custom-shell',
crawlLinks: true,
retryCount: 3,
},
},
}),
],
})
// vite.config.ts
export default defineConfig({
plugins: [
TanStackStart({
spa: {
prerender: {
outputPath: '/custom-shell',
crawlLinks: true,
retryCount: 3,
},
},
}),
],
})
自定义 SPA Shell 的 HTML 输出可能很有用,如果您想:
为了简化此过程,可以在 router 实例上找到一个 isShell() 函数。
// src/routes/root.tsx
export default function Root() {
const isShell = useRouter().isShell()
if (isShell) console.log('Rendering the shell!')
}
// src/routes/root.tsx
export default function Root() {
const isShell = useRouter().isShell()
if (isShell) console.log('Rendering the shell!')
}
您可以使用这个布尔值来根据当前路由是否为 Shell 来有条件地渲染不同的 UI。但请注意,在水合(hydrate)Shell 后,路由器将立即导航到第一个路由,并且 isShell() 将返回 false。如果不正确处理,这可能会导致未样式化内容的闪烁。
由于 Shell 是使用应用程序的 SSR 构建进行预渲染的,因此在根路由上定义的任何 loader 或特定于服务器的功能将在预渲染过程中运行,数据将包含在 Shell 中。
这意味着您可以通过使用 loader 或特定于服务器的功能在 Shell 中使用动态数据。
// src/routes/__root.tsx
export const RootRoute = createRootRoute({
loader: async () => {
return {
name: 'Tanner',
}
},
component: Root,
})
export default function Root() {
const { name } = useLoaderData()
return (
<html>
<body>
<h1>Hello, {name}!</h1>
<Outlet />
</body>
</html>
)
}
// src/routes/__root.tsx
export const RootRoute = createRootRoute({
loader: async () => {
return {
name: 'Tanner',
}
},
component: Root,
})
export default function Root() {
const { name } = useLoaderData()
return (
<html>
<body>
<h1>Hello, {name}!</h1>
<Outlet />
</body>
</html>
)
}
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。