quasar.config 文件
这里是你配置 SSR 选项的地方。比如你可以设置客户端是作为 SPA(单页应用——默认行为)还是 PWA(渐进式 Web 应用)来接管。
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 'offline.html'
*/
pwaOfflineHtmlFilename?: string;
/**
* Extend/configure the Workbox GenerateSW options
* Specify Workbox options which will be applied on top of
* `pwa > extendGenerateSWOptions()`.
* More info: https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
*
* Can directly modify the "config" parameter or
* return a new one that will be merged with the default one.
*/
pwaExtendGenerateSWOptions?: (
config: GenerateSWOptions
) => void | GenerateSWOptions | Promise<void | GenerateSWOptions>;
/**
* Extend/configure the Workbox InjectManifest options
* Specify Workbox options which will be applied on top of
* `pwa > extendInjectManifestOptions()`.
* More info: https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
*
* Can directly modify the "config" parameter or
* return a new one that will be merged with the default one.
*/
pwaExtendInjectManifestOptions?: (
config: InjectManifestOptions
) => void | InjectManifestOptions | Promise<void | InjectManifestOptions>;
/**
* Manually serialize the store state and provide it yourself
* as window.__INITIAL_STATE__ to the client-side (through a <script> tag)
* @default false
*/
manualStoreSerialization?: boolean;
/**
* Manually inject the store state into ssrContext.state
* @default false
*/
manualStoreSsrContextInjection?: boolean;
/**
* Manually handle the store hydration instead of letting Quasar CLI do it.
*
* For Pinia: store.state.value = window.__INITIAL_STATE__
*
* @default false
*/
manualStoreHydration?: boolean;
/**
* Manually call $q.onSSRHydrated() instead of letting Quasar CLI do it.
* This announces that client-side code should takeover.
* @default 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 production generated package.json
*
* Can directly modify the "pkg" parameter or
* return a new one that will be merged with the default one.
*/
extendPackageJson?: (pkg: { [index in string]: any }) =>
| void
| { [index in string]: any }
| Promise<void | { [index in string]: any }>;
/**
* Extend the Rolldown config that is used for the SSR webserver
* (which includes the SSR middlewares).
*
* 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:
你可以自由编辑这些文件。所有目录都在各自的文档页面中有详细说明(查看左侧菜单)。
注意以下几点:
If you import anything from node_modules in /src-ssr, then make sure that the package is specified in /src-ssr/package.json > “dependencies” (runtime deps) and NOT in “devDependencies” (build system deps). The “dependencies” will be embedded into your dist/.
These files are built through a separate Rolldown config. You can extend the Rolldown configuration of these files through the
/quasar.configfile:
return {
// ...
ssr: {
// ...
extendSSRWebserverConf(rolldownConf) {
// tamper with rolldownConf here
}
}
}- The
/src-ssr/server.jsfile is detailed in SSR Webserver page. Read it especially if you need to support serverless functions.
Helping SEO
开发 SSR 而非 SPA 的主要原因之一是为了 SEO。使用 Quasar Meta 插件 来管理搜索引擎所需的动态 HTML 标记,可以大大改善 SEO。
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):
export default ({ 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 }} <% } %>