警告!Beta 阶段
Quasar SSG 模式目前处于 “beta” 阶段。根据社区反馈,API 在未来可能会有所变动,因此每次升级 “@quasar/app-vite” 时请留意 release notes。
quasar.config 文件
ssg 配置节控制生成的回退文件、客户端渲染路由、PWA 接管、store 水合以及高级构建钩子。页面生成本身属于 /src-ssg/ssg-renderer 的职责。
ssg 和 ssr 配置节中的共享选项只需配置一次。当 ssg 中省略了某个共享选项时,Quasar 会使用 ssr 中显式配置的值。在 ssg 中指定的值(包括 false 或空数组)始终优先。
export default defineConfig(() => ({
ssr: {
// 也会被 SSG 使用,因为 SSG 没有覆盖它
clientSideRenderingRoutes: ['/admin/**']
},
ssg: {
error404HtmlFilename: '404.html'
}
}))这适用于 pwaOfflineHtmlFilename、clientSideRenderingRoutes、noPreloadTagRoutes 以及 manualStore* 和 manualPostHydrationTrigger 选项。pwa 选项仍然是模式特有的,这样为一个模式启用 PWA 接管不会隐式地为另一个模式启用。其他模式特有的选项和扩展钩子不共享。
典型配置只需要少量选项:
export default defineConfig(() => ({
ssg: {
// 不启用 PWA 接管
pwa: false,
// 生成 dist/ssg/404.html
error404HtmlFilename: '404.html',
/**
* 排除这些路由的预渲染,并生成 csr.html
* picomatch 模式说明:
* "/admin" 仅匹配该精确路由
* "/admin/**" 匹配该精确路由及 /admin 的所有子路由
* "/admin/*" 仅匹配 /admin 的直接子路由
* "/admin/{users,settings}" 匹配 /admin/users 和 /admin/settings 两个精确路由
*/
clientSideRenderingRoutes: ['/account/**', '/admin']
}
}))以下是完整的选项参考。大多数应用应保持手动水合和构建扩展选项的默认值。
ssg: {
/**
* 定义 SSG 渲染过程中遇到错误时的处理方式。
*
* 可选值:
* - "abort":在第一个错误时立即中止构建过程。
* - "error":累积所有错误,在最后使构建失败。
* - "warn":将错误输出到控制台,但构建仍然成功完成。
* - "ignore":静默忽略错误,仅在最后输出一条警告,
* 说明有多少页面渲染失败。
* - 自定义函数:实现你自己的错误处理逻辑。
*
* 函数示例:
* onSsgRendererError: ({ err, reason, ssgPage }) => {
* const pageId = `${ssgPage.route} ${ssgPage.label ? ` (${ssgPage.label})` : ''}`
* console.error(
* `Error rendering SSG page with route ${pageId}${reason ? `: ${reason}` : ''}`
* )
* // 可选:以错误码退出构建过程
* // 如果不退出,构建将继续并累积错误,最终构建失败。
* process.exit(1)
* }
*
* @type OnSsgRendererError
* @default 'abort'
*/
onSsgRendererError?:
| "abort"
| "error"
| "warn"
| "ignore"
| (({
err,
reason,
ssgPage
}: {
err: unknown;
reason?: string;
ssgPage: SsgPage;
}) => void | Promise<void>);
/**
* SSG 页面并发渲染的最大数量。
* 当渲染包含异步操作(如数据获取)时,可以加速渲染。
* 渲染任务在同一个 Node.js 线程中并发运行。
* 不同页面的异步操作可能会交错执行,任何模块级别的状态
* 在所有正在渲染的页面之间共享。
*
* @default 1
*/
ssgRendererConcurrency?: number;
/**
* SSG 渲染过程中,页面渲染失败时的重试次数(非负整数)。
* 重定向和未匹配的路由不会重试。
*
* @default 0
*/
ssgRendererRetryCount?: number;
/**
* SSG 渲染过程中重试之间的延迟时间(毫秒,非负整数)。
* 可以帮助避免在重试时对系统或外部资源造成过大压力。
*
* @default 1000
*/
ssgRendererRetryDelay?: number;
/**
* 控制未在 SSG 页面定义中指定 `dir` 或 `filename` 的页面所生成的文件名。
*
* 设为 true 时,路由 "/about" 会生成 "about/index.html",
* 大多数静态 Web 服务器可同时解析 "/about" 和 "/about/"。
* 设为 false 时,则生成 "about.html"。
* 根路由始终生成 "index.html"。
*
* 指定了 `dir` 或 `filename` 的页面不受此选项影响。
*
* @default true
*/
ssgRendererDirectoryIndexes?: boolean;
/**
* 404 页面使用的 html 文件名。
* 设为 false 则不生成 404 页面。
*
* 您需要正确配置 Web 服务器以在 404 错误时
* 提供此文件。
*
* 确保命名不会与 SSG 生成的 html 文件冲突!
*
* @default '404.html'
*/
error404HtmlFilename?: string | false;
/**
* 配置混合 SSG + 部分 CSR(客户端渲染)构建,
* 让客户端对某些页面使用空壳 html(就像这些页面属于 SPA),
* 由客户端代码接管并渲染页面。
*
* 仅用于生产环境。您需要正确配置 Web 服务器,
* 对未被 SSG 预渲染的页面回退到此 html 文件。
*
* 确保命名不会与 SSG 生成的 html 文件冲突!
*
* 如果构建的是 SSG+PWA 应用,可以直接使用
* `pwaOfflineHtmlFilename` 作为空壳 html 文件,
* 因为它们的内容相同。如果不复用该文件名,
* 请选择不同的名称以避免生成文件冲突。
*
* 如果未显式配置且 `clientSideRenderingRoutes`
* 不是默认值(空数组),则此选项默认为 'csr.html'。
*
* @default false | 'csr.html'
*/
clientSideRenderingHtmlFilename?: string | false;
/**
* 配置混合 SSG + 部分 CSR(客户端渲染)方案,
* 指定哪些 Vue Router 路由仅在客户端渲染。
*
* 如果未同时指定 `clientSideRenderingHtmlFilename`,
* 其默认值将变为 'csr.html'。
*
* 在生产环境中,您需要正确配置 Web 服务器,
* 对未被 SSG 预渲染的页面回退到
* `clientSideRenderingHtmlFilename`。
*
* 可以使用 picomatch 模式来匹配要在客户端渲染的路由。
* https://www.npmjs.com/package/picomatch
*
* picomatch 模式说明:
* "/admin" 仅匹配该精确路由
* "/admin/**" 匹配该精确路由及 /admin 的所有子路由
* "/admin/*" 仅匹配 /admin 的直接子路由
* "/admin/{users,settings}" 匹配 /admin/users 和 /admin/settings 两个精确路由
*
* @example ['/dashboard', '/admin/**']
* @default ssr.clientSideRenderingRoutes(如已配置),否则 []
*/
clientSideRenderingRoutes?: string[];
/**
* 配置不希望 SSG 渲染器在生成的 HTML 中注入预加载标签的
* Vue Router 路由。
*
* 可以使用 picomatch 模式来匹配不需要预加载标签的路由。
* https://www.npmjs.com/package/picomatch
*
* picomatch 模式说明:
* "/admin" 仅匹配该精确路由
* "/admin/**" 匹配该精确路由及 /admin 的所有子路由
* "/admin/*" 仅匹配 /admin 的直接子路由
* "/admin/{users,settings}" 匹配 /admin/users 和 /admin/settings 两个精确路由
*
* @example ['/dashboard', '/admin/**']
* @default ssr.noPreloadTagRoutes(如已配置),否则 []
*/
noPreloadTagRoutes?: string[];
/**
* 扩展用于 SSG 渲染器(即 /src-ssg/ssg-renderer 文件)
* 的 Rolldown 配置。
*
* 可以是异步的。可以直接修改 "config" 参数,
* 或返回一个新对象与默认配置合并。
*/
extendSSGRendererConf?: (
config: RolldownOptions
) => void | RolldownOptions | Promise<void | RolldownOptions>;
/**
* 扩展 Vite 生成的底层 SSR manifest 文件,
* 服务端渲染器使用它来确定需要预加载哪些文件。
*
* 可以是异步的。可以直接修改 "ssrManifest" 参数,
* 或返回一个新对象与默认配置合并。
*/
extendSSGManifestJson?: (
ssrManifest: QuasarSsrManifest
) => void | QuasarSsrManifest | Promise<void | QuasarSsrManifest>;
/**
* 是否由 PWA 接管客户端,否则为 SPA。
* @default false
*/
pwa?: boolean;
/**
* 使用 SSG+PWA 时,这是 PWA 客户端回退的
* index html 文件名。仅用于生产环境。
*
* 确保命名不会与 SSG 生成的 html 文件冲突。
* 它可以有意地与 `clientSideRenderingHtmlFilename` 相同,
* 以复用同一个应用外壳。
*
* @default ssr.pwaOfflineHtmlFilename(如已配置),否则 'offline.html'
*/
pwaOfflineHtmlFilename?: string;
/**
* 扩展/配置 Workbox GenerateSW 选项
* 指定将应用在 `pwa > extendPWAGenerateSWOptions()` 之上的
* Workbox 选项。
*
* https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
*
* 可以是异步的。可以直接修改 "config" 参数,
* 或返回一个新对象与默认配置合并。
*/
extendSSGGenerateSWOptions?: (
config: GenerateSWOptions
) => void | GenerateSWOptions | Promise<void | GenerateSWOptions>;
/**
* 扩展/配置 Workbox InjectManifest 选项
* 指定将应用在 `pwa > extendPWAInjectManifestOptions()` 之上的
* Workbox 选项。
*
* https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
*
* 可以是异步的。可以直接修改 "config" 参数,
* 或返回一个新对象与默认配置合并。
*/
extendSSGInjectManifestOptions?: (
config: InjectManifestOptions
) => void | InjectManifestOptions | Promise<void | InjectManifestOptions>;
/**
* 手动序列化 store 状态,并自行通过 <script> 标签
* 将其作为 window.__INITIAL_STATE__ 提供给客户端。
* @default ssr.manualStoreSerialization(如已配置),否则 false
*/
manualStoreSerialization?: boolean;
/**
* 手动将 store 状态注入 ssrContext.state
* @default ssr.manualStoreSsrContextInjection(如已配置),否则 false
*/
manualStoreSsrContextInjection?: boolean;
/**
* 手动处理 store 水合,而不是让 Quasar CLI 自动完成。
*
* 对于 Pinia:store.state.value = window.__INITIAL_STATE__
*
* @default ssr.manualStoreHydration(如已配置),否则 false
*/
manualStoreHydration?: boolean;
/**
* 手动调用 $q.onSSRHydrated() 而不是让 Quasar CLI 自动调用。
* 此调用宣告客户端代码接管。
* @default ssr.manualPostHydrationTrigger(如已配置),否则 false
*/
manualPostHydrationTrigger?: boolean;
}如果启用了 PWA 客户端接管,Quasar CLI 也会安装 PWA 模式。请阅读 SSG 配合 PWA 和 Quasar PWA 指南。
如果希望应用的某些路由仅在客户端渲染,混合 SSG + 部分 CSR 正是为此而生。
要扩展用于构建 /src 下应用代码的 Vite 配置,使用常规的 build.extendViteConf 钩子并检查 SSG 模式:
export default defineConfig(ctx => {
return {
build: {
extendViteConf(viteConf, { isClient, isServer }) {
if (ctx.mode.ssg) {
// 对 viteConf 做一些处理
// 或返回一个对象与当前 viteConf 深度合并
}
}
}
}
})手动触发 store 水合
默认情况下,Quasar CLI 将 Pinia 状态序列化到生成的 HTML 中,并在客户端水合 store。
仅当您需要替换客户端水合步骤时,才设置 ssg.manualStoreHydration: true。例如使用一个仅在客户端运行的 boot 文件:
// 确保将此 BOOT 文件配置为
// 仅在客户端运行
import { defineBoot } from '#q-app'
export default defineBoot(({ store }) => {
// 对于 Pinia
store.state.value = window.__INITIAL_STATE__
})手动触发水合后处理
默认情况下,Quasar CLI 会包裹您的 App 组件,并在客户端挂载此包裹组件时调用 $q.onSSRHydrated()。这标志着客户端接管。您无需做任何配置。
对于需要控制此时机的高级集成,设置 ssg.manualPostHydrationTrigger: true 并在挂载后自行调用:
// App.vue
import { onMounted } from 'vue'
import { useQuasar } from 'quasar'
export default {
// ....
setup () {
// ...
const $q = useQuasar()
onMounted(() => {
$q.onSSRHydrated()
})
}
}SSG 渲染器
在 Quasar 项目中添加 SSG 模式意味着会创建一个新目录:/src-ssg,其中包含 SSG 专属文件,如 ssg 渲染器脚本:
重要细节:
被
/src-ssg/ssg-renderer直接引入的包必须列在/src-ssg/package.json中并在该目录下安装。渲染器使用独立的 Rolldown 配置构建。仅当渲染器需要自定义构建行为时,才通过
/quasar.config扩展它:
ssg: {
/**
* 扩展用于 SSG 渲染器(即 /src-ssg/ssg-renderer 文件)
* 的 Rolldown 配置。
*
* 可以是异步的。可以直接修改 "config" 参数,
* 或返回一个新对象与默认配置合并。
*/
extendSSGRendererConf?: (
config: RolldownOptions
) => void | RolldownOptions | Promise<void | RolldownOptions>;
}- 关于渲染器 API 和页面示例,请参阅 SSG 渲染器。
SEO 优化
使用 Quasar Meta 插件 在生成的 HTML 中包含路由特定的标题、描述、canonical 链接和社交媒体元数据。详见 SSG 的 SEO。
Boot 文件
在 SSG 模式下,应用代码必须是通用的(universal):它在生产构建时运行于 Node.js 中,水合时又在浏览器中运行。Boot 文件 同样如此。
明确标记仅浏览器或仅构建时的 boot 文件:
return {
// ...
boot: [
'some-boot-file', // 在服务端和客户端都运行
{ path: 'some-other', server: false }, // 此 boot 文件仅嵌入客户端
{ path: 'third', client: false } // 此 boot 文件仅嵌入服务端
]
}仅服务端的 boot 文件在构建时为每个渲染页面运行。它们不会在生产静态主机上运行。
当 boot 文件在服务端生成期间运行时,其回调会接收 ssrContext:
import { defineBoot } from '#q-app'
export default defineBoot(({ ssrContext }) => {
ssrContext.someProp = 'some value'
})当 /index.html 引用了自定义值时,用模式判断来守护它,并确保每个适用的页面定义都提供了该值:
<% if (ctx.mode.ssg) { %>{{ ssrContext.someProp }} <% } %>