警告!Beta 阶段
Quasar SSG 模式目前处于 “beta” 阶段。根据社区反馈,API 在未来可能会有所变动,因此每次升级 “@quasar/app-vite” 时请留意 release notes。
/src-ssg/ssg-renderer 文件告诉 Quasar 在生产构建时渲染哪些路由。它也可以自定义为每个页面资源添加的预加载标签。
WARNING
SSG 渲染器仅在 quasar build -m ssg 时运行。开发模式按需渲染请求的路由,不会调用 getSsgPages()。
结构
该文件导出 getSsgPages,可选导出 renderPreloadTag。生成的模板包含两者:
import { defineSsgGetPages, defineSsgRenderPreloadTag } from '#q-app'
import routes from '@/router/routes'
export const getSsgPages = defineSsgGetPages(({ parseVueRouterRoutes /*, ctx */ }) => {
// parseVueRouterRoutes 的使用是可选的,它只是一个辅助函数。
const { ssgPages } = parseVueRouterRoutes({ routes, verbose: true })
return ssgPages
})
const jsRE = /\.js$/
const cssRE = /\.css$/
const woffRE = /\.woff$/
const woff2RE = /\.woff2$/
const gifRE = /\.gif$/
const jpgRE = /\.jpe?g$/
const pngRE = /\.png$/
export const renderPreloadTag = defineSsgRenderPreloadTag(
(file /* , { ssrContext } */) => {
if (jsRE.test(file)) {
return `<link rel="modulepreload" href="${file}" crossorigin>`
}
if (cssRE.test(file)) {
return `<link rel="stylesheet" href="${file}" crossorigin>`
}
if (woffRE.test(file)) {
return `<link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
}
if (woff2RE.test(file)) {
return `<link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
}
if (gifRE.test(file)) {
return `<link rel="preload" href="${file}" as="image" type="image/gif" crossorigin>`
}
if (jpgRE.test(file)) {
return `<link rel="preload" href="${file}" as="image" type="image/jpeg" crossorigin>`
}
if (pngRE.test(file)) {
return `<link rel="preload" href="${file}" as="image" type="image/png" crossorigin>`
}
return ''
}
)定义 SSG 页面
getSsgPages 导出使用 defineSsgGetPages 包装器。它返回一个页面定义数组,可以是同步或异步的。当数组为空时构建会中止。
type SsgGetPagesCallback = (
params: SsgGetPagesParams
) => SsgPage[] | Promise<SsgPage[]>;
interface SsgGetPagesParams {
/**
* Quasar 构建上下文。
* 与 /quasar.config 文件中的相同。您可以使用它来访问
* ctx.appPaths(以及其他属性)来解析页面路径,当使用
* tinyglobby 等工具手动读取文件系统时尤其有用。
*
* @type QuasarContext
*/
readonly ctx: QuasarContext;
/**
* Quasar SSG 配置 (quasar.config file > ssg)
* @type QuasarSsgConfiguration
*/
readonly quasarConfSsg: QuasarSsgConfiguration;
/**
* 内置辅助函数,解析 Vue Router 路由并自动构建待生成的路由列表。
* 它会忽略重定向、带参数的路由以及 CSR 定义的路由。
* 如果需要,您需要手动定义并添加这些 SSG 页面。
*
* @param {SsgParseVueRouterParams} options - 配置对象。
* @param {RouteRecordRaw[]} options.routes - 要解析的 Vue Router 路由定义。
* @param {string} [options.parentPath='/'] - 可选的父路径。
* @param {string[]} [options.crawlIgnoreRoutes=[]] - 可选的 picomatch 模式,匹配的路由会被省略,但其子路由仍会被遍历。
* @param {boolean} [options.verbose=false] - 可选标志,启用详细日志。如果为 true,会记录被忽略的带动态参数的路由。
* @returns {SsgParseVueRouterResult}
*/
parseVueRouterRoutes: SsgParseVueRouterRoutes;
/**
* 使用基于文件名的路由时的辅助函数,返回 Vue Router 自动生成的路由。
* 仅在 quasar.config > build > filenameBasedRouting 设为 true 时可用。
*
* @returns {Promise<RouteRecordRaw[]>} Vue Router 路由数组。
*/
getFilenameBasedRoutes: SsgGetFilenameBasedRoutes;
}
type SsgParseVueRouterRoutes = (
params: SsgParseVueRouterParams
) => SsgParseVueRouterResult;
type SsgParseVueRouterParams = {
/**
* 要解析的 Vue Router 路由定义。
*/
routes: RouteRecordRaw[];
/**
* 可选的父路径。
* @default '/'
*/
parentPath?: string;
/**
* 解析过程中要忽略的路由列表(可选)。
* 可以使用 picomatch 模式来匹配要忽略的路由。
* 匹配的路由会被省略,但其子路由仍会被遍历并
* 按模式评估。
* https://www.npmjs.com/package/picomatch
*
* picomatch 模式说明:
* "/admin" 仅匹配该精确路由
* "/admin/**" 匹配该精确路由及 /admin 的所有子路由
* "/admin/*" 仅匹配 /admin 的直接子路由
* "/admin/{users,settings}" 匹配 /admin/users 和 /admin/settings 两个精确路由
*
* @example ['/dashboard', '/admin/**']
* @default []
*/
crawlIgnoreRoutes?: string[];
/**
* 动态路由段的可选动态参数列表。
* 数组中的每个条目应为一个对象,键是动态段的名称,值是该段对应的值。
*
* 关于可选路由参数(如 /user/:id?)的注意事项:
* 对可选参数使用 { [name]: "" }(空字符串作为值)。不要在对象中省略任何可选参数。
* 示例:{ "/user/:id?": [{ id: "" }] } 会为 /user 路由生成 SSG 页面。
*
* @example { "/user/:id": [{ id: 1 }, { id: 2 }] }
* @example { "/product/:category/:id": [{ category: "electronics", id: 123 }, { category: "books", id: 456 }] }
* @default {}
*/
routesDynamicParamsMap?: Record<
string,
Array<Record<string, string | number>>
>;
/**
* 可选标志,启用详细日志。
* 如果为 true,会记录被忽略的带动态参数的路由。
*
* @default false
*/
verbose?: boolean;
};
type SsgParseVueRouterResult = {
/**
* 基于解析的 Vue Router 路由生成的 SSG 页面。
*/
ssgPages: SsgPage[];
/**
* 指示解析过程中是否存在被忽略的路由。
*/
hasIgnoredRoutes: boolean;
/**
* 因匹配 crawlIgnoreRoutes 模式而被忽略的 Vue Router 路由列表。
*/
crawlIgnoredSsgPages: SsgPage[];
/**
* 因为是重定向而被忽略的 Vue Router 路由列表。
*/
ignoredRedirectSsgPages: SsgPage[];
/**
* 因具有动态参数而被忽略的 Vue Router 路由列表。
*/
ignoredDynamicParamSsgPages: SsgPage[];
/**
* 因被标记为客户端渲染 (CSR) 而被忽略的 Vue Router 路由列表。
*/
ignoredCsrSsgPages: SsgPage[];
};WARNING
定义 SSG 页面的 route 属性时,不要包含 quasar.config > build.publicPath。仅作为 Vue Router 路由使用。
import { defineSsgGetPages } from '#q-app'
import products from '@/data/products.json'
export const getSsgPages = defineSsgGetPages(() => [
{ route: '/' },
...products.map(product => ({
route: `/products/${product.slug}`,
label: product.name
}))
])关于 404 错误的提示
您的应用通常会有一个 Vue Router 兜底路由:
// 使用 Vue Router 捕获 404 的路由示例
{ path: '/:catchAll(.*)*', component: () => import('@/pages/Error404.vue') }如果页面定义包含一个不存在的路由,Vue Router 会将其解析到这个兜底路由,Quasar 将写入渲染出的 404 标记。这是有效的渲染,所以构建无法推断该路由是否是个错误。
Quasar 会单独生成配置的 SSG 404 错误页面,因此不要为默认的 404.html 添加页面定义。
渲染预加载标签
Quasar 可以为每个生成页面使用的资源注入 <link rel="preload"> 和 <link rel="modulepreload"> 标签。renderPreloadTag 接收每个资源 URL 并返回其 HTML 标签。
生成的渲染器处理 JavaScript、CSS、WOFF 字体和常见图片格式。自定义它以省略昂贵的图片预加载或支持其他资源类型。
import { defineSsgRenderPreloadTag } from '#q-app'
const jsRE = /\.js$/
const cssRE = /\.css$/
const woffRE = /\.woff$/
const woff2RE = /\.woff2$/
const gifRE = /\.gif$/
const jpgRE = /\.jpe?g$/
const pngRE = /\.png$/
export const renderPreloadTag = defineSsgRenderPreloadTag(
(file, { ssrContext }) => {
if (jsRE.test(file)) {
return `<link rel="modulepreload" href="${file}" crossorigin>`
}
if (cssRE.test(file)) {
return `<link rel="stylesheet" href="${file}" crossorigin>`
}
if (woffRE.test(file)) {
return `<link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
}
if (woff2RE.test(file)) {
return `<link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
}
if (gifRE.test(file)) {
return `<link rel="preload" href="${file}" as="image" type="image/gif" crossorigin>`
}
if (jpgRE.test(file)) {
return `<link rel="preload" href="${file}" as="image" type="image/jpeg" crossorigin>`
}
if (pngRE.test(file)) {
return `<link rel="preload" href="${file}" as="image" type="image/png" crossorigin>`
}
return '' // 不需要预加载标签时返回空字符串
}
)始终确保根据您的 CORS 设置和资源托管位置正确应用 crossorigin。默认模板假设使用标准的本地托管或标准 CDN 托管。