Special handling for server-side rendering
Special Handling for Server-Side Rendering
Vue 3's Server-Side Rendering (SSR) differs significantly from client-side rendering, with the core challenge lying in how to handle component lifecycles, state management, and DOM operations across different environments. The runtime must distinguish the current execution context and replace or downgrade specific APIs accordingly.
Environment Variables and Global State Isolation
The primary issue in SSR is avoiding singleton state pollution. In a Node.js environment, each request should receive a fresh application instance:
// Use a factory function to avoid singletons
export const createApp = () => {
const app = createSSRApp(App)
const store = createStore()
app.use(store)
return { app, store }
}
Vue 3 uses the __VUE_PROD_SSR__
compilation flag to distinguish environments. Internally, it automatically handles references to client-specific objects like window
. For global variable access, it is recommended to use ssrContext
injection:
// Set context on the server
const ctx = {}
const { app } = createApp()
renderToString(app, ctx)
// Access in components
import { useSSRContext } from 'vue'
const ctx = inject(SSR_CONTEXT)
Lifecycle Hook Adjustments
Only specific lifecycle hooks are executed during SSR, requiring special attention:
beforeCreate
andcreated
run on the server.beforeMount
andmounted
execute only on the client.beforeUpdate
andupdated
do not run on the server.activated
anddeactivated
requireKeepAlive
to function.
Async components need special handling:
defineAsyncComponent({
loader: () => import('./Component.vue'),
suspensible: false // Disable Suspense wrapping
})
Template Compilation Differences
During SSR, the compiler generates optimized string-concatenation code. The compileSSR
method produces a different render function:
const { compile } = require('vue/compiler-ssr')
const result = compile(`<div>{{ msg }}</div>`, {
isCustomElement: tag => tag.startsWith('x-')
})
Key differences include:
- Skipping event listener compilation
- Omitting virtual DOM patch flags
- Hoisting static nodes as string constants
- Disabling two-way binding syntax like
v-model
Client-Side Hydration Handling
For hybrid rendering, ensure proper client-side hydration. createSSRApp
generates special markers:
<div id="app" data-server-rendered="true">
<!-- Server-rendered content -->
</div>
The hydration process compares existing DOM with virtual DOM. Key logic resides in packages/runtime-core/src/renderer.ts
:
if (container._vnode) {
// Hydrate existing DOM
hydrate(container._vnode, container)
} else {
// Fresh mount
render(vnode, container)
}
Data Prefetching and State Synchronization
Server-side data prefetching requires special handling to avoid duplicate client requests:
// Server-side router configuration
router.beforeResolve(async (to, from, next) => {
const matched = to.matched.flatMap(record => {
return Object.values(record.components).map(component => {
return component.asyncData?.({ store, route: to })
})
})
await Promise.all(matched)
next()
})
// Client-side state synchronization
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
Streaming Rendering
For high-performance scenarios, streaming rendering can be used via renderToStream
:
const stream = renderToStream(app)
stream.on('data', chunk => {
res.write(chunk)
})
stream.on('end', () => {
res.end()
})
Streaming requires careful component ordering. Async components must be declared in advance:
defineComponent({
ssrPrefetch: true, // Force preloading
async setup() {
const data = await fetchData()
return { data }
}
})
Cross-Platform API Adaptation
Vue 3 provides platform-agnostic rendering APIs via @vue/server-renderer
, implementing basic cross-platform methods:
interface RendererOptions {
createElement: (tag: string) => any
insert: (el: any, parent: any) => void
setElementText: (el: any, text: string) => void
patchProp: (el: any, key: string, prevValue: any, nextValue: any) => void
}
For specialized environments like mini-programs, these interfaces can be customized.
Error Handling and Fallback Strategies
SSR requires robust error-catching mechanisms:
try {
const html = await renderToString(app)
} catch (err) {
// Log errors
console.error('SSR error:', err)
// Fall back to client-side rendering
res.send(`<div id="app"></div>`)
}
For third-party library compatibility, mock browser objects with ssrMock
:
global.window = {
localStorage: {
getItem: () => null
}
}
Performance Optimization Strategies
SSR performance hinges on caching and reuse:
const LRU = require('lru-cache')
const microCache = new LRU({
max: 100,
maxAge: 1000 * 60 // 1-minute cache
})
function render(req, res) {
const cacheKey = req.url
if (microCache.has(cacheKey)) {
return res.send(microCache.get(cacheKey))
}
renderToString(app).then(html => {
microCache.set(cacheKey, html)
res.send(html)
})
}
Component-level caching is achieved via serverCacheKey
:
export default {
name: 'CachedComponent',
props: ['item'],
serverCacheKey: props => props.item.id
}
Build Configuration Differences
SSR builds require distinct resources:
// vue.config.js
module.exports = {
chainWebpack: config => {
config.entry('app').clear().add('./src/entry-client.js')
config.entry('server').clear().add('./src/entry-server.js')
}
}
Webpack must handle Node.js modules:
externals: [
nodeExternals({
allowlist: [/\.css$/, /\?vue&type=style/]
})
]
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:异步渲染的实现原理
下一篇:自定义渲染器的扩展机制