quasar.config 文件
这里是你配置 SSR 选项的地方。比如你可以设置客户端是作为 SPA(单页应用——默认行为)还是 PWA(渐进式 Web 应用)来接管。
ssr 和 ssg 配置节中的共享选项只需配置一次。当 ssr 中省略了某个共享选项时,Quasar 会使用 ssg 中显式配置的值。在 ssr 中指定的值(包括 false 或空数组)始终优先。
export default defineConfig(() => ({
ssr: {
prodPort: 3000
},
ssg: {
// 也会被 SSR 使用,因为 SSR 没有覆盖它
clientSideRenderingRoutes: ['/admin/**']
}
}))这适用于 pwaOfflineHtmlFilename、clientSideRenderingRoutes、noPreloadTagRoutes 以及 manualStore* 和 manualPostHydrationTrigger 选项。pwa 选项仍然是模式特有的,这样为一个模式启用 PWA 接管不会隐式地为另一个模式启用。其他模式特有的选项和扩展钩子不共享。
return {
// ...
ssr: {
/**
* If a PWA should take over or just a SPA.
* @default false
*/
pwa?: boolean;
/**
* When using SSR+PWA, this is the name of the
* PWA index html file that the client-side fallbacks to.
* For production only.
*
* Do NOT use index.html as name as it will mess SSR up!
*
* @default ssg.pwaOfflineHtmlFilename (when configured), otherwise 'offline.html'
*/
pwaOfflineHtmlFilename?: string;
/**
* Extend/configure the Workbox GenerateSW options
* Specify Workbox options which will be applied on top of
* `pwa > extendPWAGenerateSWOptions()`.
*
* https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
*
* Can be async. Can directly modify the "config" parameter or
* return a new one that will be merged with the default one.
*/
extendSSRGenerateSWOptions?: (
config: GenerateSWOptions
) => void | GenerateSWOptions | Promise<void | GenerateSWOptions>;
/**
* Extend/configure the Workbox InjectManifest options
* Specify Workbox options which will be applied on top of
* `pwa > extendPWAInjectManifestOptions()`.
*
* https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
*
* Can be async. Can directly modify the "config" parameter or
* return a new one that will be merged with the default one.
*/
extendSSRInjectManifestOptions?: (
config: InjectManifestOptions
) => void | InjectManifestOptions | Promise<void | InjectManifestOptions>;
/**
* Requires @quasar/app-vite v3.1+
*
* Configure this for a hybrid SSR + partial CSR (Client-Side Rendering)
* approach, where you have some Vue Router routes that you want to be
* rendered on the client-side exclusively.
*
* You can use picomatch patterns to match the routes you want to be rendered
* on the client-side. https://www.npmjs.com/package/picomatch
*
* Note on picomatch patterns:
* "/admin" matches the exact route only
* "/admin/**" matches the exact route and all sub-routes of /admin
* "/admin/*" matches only direct sub-routes of /admin
* "/admin/{users,settings}" matches both exact routes /admin/users and /admin/settings
*
* @example ['/dashboard', '/admin/**']
* @default ssg.clientSideRenderingRoutes (when configured), otherwise []
*/
clientSideRenderingRoutes?: string[];
/**
* Requires @quasar/app-vite v3.1+
*
* Configure the Vue Router routes for which you don't want to inject
* preload tags (on production only!).
*
* You can use picomatch patterns to match the routes you want
* no preload tags for. https://www.npmjs.com/package/picomatch
*
* Note on picomatch patterns:
* "/admin" matches the exact route only,
* "/admin/**" matches the exact route and all sub-routes of /admin,
* "/admin/*" matches only direct sub-routes of /admin,
* "/admin/{users,settings}" matches both exact routes /admin/users and /admin/settings
*
* @example ['/dashboard', '/admin/**']
* @default ssg.noPreloadTagRoutes (when configured), otherwise []
*/
noPreloadTagRoutes?: string[];
/**
* Manually serialize the store state and provide it yourself
* as window.__INITIAL_STATE__ to the client-side (through a <script> tag)
* @default ssg.manualStoreSerialization (when configured), otherwise false
*/
manualStoreSerialization?: boolean;
/**
* Manually inject the store state into ssrContext.state
* @default ssg.manualStoreSsrContextInjection (when configured), otherwise false
*/
manualStoreSsrContextInjection?: boolean;
/**
* Manually handle the store hydration instead of letting Quasar CLI do it.
*
* For Pinia: store.state.value = window.__INITIAL_STATE__
*
* @default ssg.manualStoreHydration (when configured), otherwise false
*/
manualStoreHydration?: boolean;
/**
* Manually call $q.onSSRHydrated() instead of letting Quasar CLI do it.
* This announces that client-side code should takeover.
* @default ssg.manualPostHydrationTrigger (when configured), otherwise false
*/
manualPostHydrationTrigger?: boolean;
/**
* The default port (3000) that the production server should use
* (gets superseded if process.env.PORT is specified at runtime)
* @default 3000
*/
prodPort?: number;
/**
* List of middleware files in src-ssr/middlewares
* Order is important.
*/
middlewares?: string[];
/**
* Add/remove/change properties of SSR production generated package.json
*
* Can be async. Can directly modify the "pkgJson" parameter or
* return a new one that will be merged with the default one.
*/
extendSSRPackageJson?: (pkgJson: { [index in string]: any }) =>
| void
| { [index in string]: any }
| Promise<void | { [index in string]: any }>;
/**
* Requires @quasar/app-vite v3.1+
*
* Extend the underlying SSR manifest file generated by Vite,
* which is used by the server-side renderer to know which files to preload.
*
* Can be async. Can directly modify the "ssrManifest" parameter or
* return a new one that will be merged with the default one.
*/
extendSSRManifestJson?: (
ssrManifest: QuasarSsrManifest
) => void | QuasarSsrManifest | Promise<void | QuasarSsrManifest>;
/**
* Extend the Rolldown config that is used for the SSR webserver
* (which includes the SSR middlewares).
*
* Can be async. Can directly modify the "config" parameter or
* return a new one that will be merged with the default one.
*/
extendSSRWebserverConf?: (
config: RolldownOptions
) => void | RolldownOptions | Promise<void | RolldownOptions>;
/**
* The named exports to use for the production generated SSR index.js script.
* Works with `false` (no named exports), a single string (one named export),
* or an array of strings (multiple named exports).
*
* Useful for serverless environments where you might want to export the
* handler function. It creates one or more named exports from the
* object returned by the defineSsrListen() function in /src-ssr/server file.
*
* @default false
*
* @example
* prodScriptNamedExport: ['handler', 'ssr']
* export const listen = defineSsrListen(() => {
* if (import.meta.env.QUASAR_PROD) {
* return { handler, ssr }
* }
* })
*
* This will generate an SSR index.js with the following exports:
* const { handler, ssr } = await listen({...})
* export { handler, ssr }
*
* @example
* prodScriptNamedExport: 'default'
* export const listen = defineSsrListen(({ app }) => {
* if (import.meta.env.QUASAR_PROD) {
* return { default: app }
* }
* })
*
* This will generate an SSR index.js with the following exports:
* const listenResult = await listen({...})
* export default listenResult?.default
*
* @example
* prodScriptNamedExport: 'app'
* export const listen = defineSsrListen(({ app }) => {
* if (import.meta.env.QUASAR_PROD) {
* return { app }
* }
* })
*
* This will generate an SSR index.js with the following exports:
* const { app } = await listen({...})
* export { app }
*
* @example 'renderSsrContext' (special case)
*
* This will generate an SSR index.js with the following export:
* export { render as renderSsrContext }
* where "render" is the same function used in
* the /src-ssr/middlewares/render file
*/
prodScriptNamedExport?: false | string | string[];
}
}If you decide to go with a PWA client takeover (which is a killer combo), the Quasar CLI PWA mode will be installed too. You may want to check out the Quasar PWA guide too. But most importantly, make sure you read SSR with PWA page.
如果你想修改 /src 中 UI 的 Vite 配置:
export default defineConfig(ctx => {
return {
build: {
extendViteConf(viteConf, { isClient, isServer }) {
if (ctx.mode.ssr) {
// do something with viteConf
// or return an object to deeply merge with current viteConf
}
}
}
}
})Manually triggering store hydration
默认情况下,Quasar CLI 会在客户端处理 Pinia store 的 hydration(如果你使用了 Pinia)。
但是,如果你希望手动进行 hydration,需要设置 quasar.config file > ssr > manualStoreHydration: true。一个好的例子是在 boot 文件 中进行:
// MAKE SURE TO CONFIGURE THIS BOOT FILE
// TO RUN ONLY ON CLIENT-SIDE
import { defineBoot } from '#q-app'
export default defineBoot(({ store }) => {
// For Pinia
store.state.value = window.__INITIAL_STATE__
})Manually triggering post-hydration
By default, Quasar CLI wraps your App component and calls $q.onSSRHydrated() on the client-side when this wrapper component gets mounted. This is the moment that the client-side takes over. You don’t need to configure anything for this to happen.
但是如果你希望覆盖这个时机,需要设置 quasar.config file > ssr > manualPostHydrationTrigger: true。无论你的原因是什么(非常自定义的用例),以下是手动触发 post hydration 的示例:
// App.vue
import { onMounted } from 'vue'
import { useQuasar } from 'quasar'
export default {
// ....
setup () {
// ...
const $q = useQuasar()
onMounted(() => {
$q.onSSRHydrated()
})
}
}Node.js Webserver
Adding SSR mode to a Quasar project means a new folder will be created: /src-ssr, which contains SSR specific files for the actual Node.js webserver:
你可以自由编辑这些文件。所有目录都在各自的文档页面中有详细说明(查看左侧菜单)。
注意以下几点:
/src-ssr中导入的运行时依赖包必须列在/src-ssr/package.json > dependencies中(而不是devDependencies)。Quasar 会将其复制到生产环境生成的package.json中,以便在部署时安装。These files are built through a separate Rolldown config. You can extend the Rolldown configuration of these files through the
/quasar.configfile:
return {
// ...
ssr: {
/**
* Extend the Rolldown config that is used for the SSR webserver
* (which includes the SSR middlewares).
*
* Can be async. Can directly modify the "config" parameter or
* return a new one that will be merged with the default one.
*/
extendSSRWebserverConf?: (
config: RolldownOptions
) => void | RolldownOptions | Promise<void | RolldownOptions>;
}
}/src-ssr/server.js文件的详细说明在 SSR Webserver 页面。如果需要支持 serverless 函数,请务必阅读。
Helping SEO
SSR 允许爬虫接收每个路由的渲染内容。使用 Quasar Meta 插件 添加搜索引擎所需的页面标题、描述、canonical URL 和结构化数据。
Boot Files
在 SSR 模式下,共享的应用代码必须能同时在 Node.js 和浏览器上下文中运行。Boot 文件 同样如此。
但是,有些情况下你只想让某些 boot 文件仅在服务端或仅在客户端运行。你可以通过以下方式实现:
return {
// ...
boot: [
'some-boot-file', // runs on both server and client
{ path: 'some-other', server: false }, // this boot file gets embedded only on client-side
{ path: 'third', client: false } // this boot file gets embedded only on server-side
]
}不过请确保你的应用保持一致性。
当 boot 文件在服务端运行时,默认导出函数会多一个参数(称为 ssrContext):
import { defineBoot } from '#q-app'
export default defineBoot(({ app, ..., ssrContext }) => {
// You can add props to the ssrContext then use them in the /index.html.
// Example - let's say we ssrContext.someProp = 'some value', then in index template we can reference it:
// {{ ssrContext.someProp }}
})When you add such references into your /index.html, make sure you tell Quasar it’s only valid for SSR builds:
<% if (ctx.mode.ssr) { %>{{ ssrContext.someProp }} <% } %>