13.1 分页数据预取实现
13.1.1 路由参数处理
// 服务端入口文件:entry-server.js
export default context => {
return new Promise((resolve, reject) => {
const { app, router, store } = createApp()
// 将客户端请求的URL推送到路由实例
router.push(context.url)
router.onReady(() => {
// 获取匹配的组件列表(数组形式)
const matchedComponents = router.getMatchedComponents()
// 404处理:没有匹配的组件
if (!matchedComponents.length) {
return reject({ code: 404 })
}
// 遍历所有匹配组件,收集需要预取的数据Promise
Promise.all(matchedComponents.map(Component => {
// 检查组件是否定义了asyncData方法
if (Component.asyncData) {
// 调用asyncData并传入store和当前路由信息
// 注意:需要处理可能的reject情况
return Component.asyncData({
store,
route: router.currentRoute // 包含查询参数等路由信息
})
}
})).then(() => {
// 所有数据预取完成后,将状态注入context
context.state = store.state
resolve(app)
}).catch(reject)
}, reject)
})
}
13.1.2 分页组件实现
// components/ArticleList.vue
export default {
name: 'ArticleList',
asyncData({ store, route }) {
// 从路由查询参数中获取页码,默认为1
const page = Number(route.query.page) || 1
// 调用Vuex action获取数据,返回Promise
return store.dispatch('fetchArticles', {
page,
perPage: 10 // 每页数量
})
},
computed: {
articles() {
return this.$store.state.articles.list
},
totalPages() {
return this.$store.state.articles.totalPages
}
},
watch: {
// 监听路由变化,客户端切换页面时获取数据
'$route'() {
// 重新执行数据获取(客户端混合后)
this.loadData()
}
},
methods: {
loadData() {
const page = Number(this.$route.query.page) || 1
this.$store.dispatch('fetchArticles', { page })
}
}
}
13.2 认证状态处理
13.2.1 用户认证中间件
// middleware/auth.js
export default function ({ store, redirect }) {
// 服务端检查用户认证状态
if (!store.state.user.isAuthenticated) {
// 未认证用户重定向到登录页
return redirect('/login')
}
}
// 在组件中使用
export default {
middleware: 'auth',
// ...其他组件选项
}
13.2.2 认证数据预取
// store/user.js
export const actions = {
async checkAuth({ commit }, { req }) {
// 服务端从请求头获取cookie
if (process.server && req.headers.cookie) {
// 解析cookie获取token
const parsed = parseCookies(req)
if (parsed.token) {
// 验证token有效性
const user = await verifyToken(parsed.token)
commit('SET_USER', user)
}
}
// 客户端从localStorage获取token
if (process.client) {
const token = localStorage.getItem('token')
if (token) {
const user = await verifyToken(token)
commit('SET_USER', user)
}
}
}
}
// 在全局导航守卫中调用
router.beforeEach(async (to, from, next) => {
await store.dispatch('checkAuth', { req: context.req })
next()
})
13.3 缓存策略优化
13.3.1 页面级别缓存
// 服务端缓存配置
const LRU = require('lru-cache')
const microCache = new LRU({
max: 100, // 最大缓存项数
maxAge: 1000 * 60 // 缓存有效期1分钟
})
// 请求处理中间件
server.get('*', (req, res) => {
const cacheKey = req.url
const hit = microCache.get(cacheKey)
// 命中缓存直接返回
if (hit) {
console.log(`Cache hit: ${cacheKey}`)
return res.end(hit)
}
renderer.renderToString(context, (err, html) => {
if (err) {
// 错误处理...
}
// 缓存HTML结果
microCache.set(cacheKey, html)
res.end(html)
})
})
13.3.2 组件级别缓存
// 创建带缓存的renderer
const renderer = createBundleRenderer(serverBundle, {
cache: {
get: key => componentCache.get(key),
set: (key, val) => componentCache.set(key, val)
}
})
// 组件定义缓存策略
export default {
name: 'HeavyComponent',
props: ['id'],
serverCacheKey: props => props.id, // 根据props生成唯一缓存key
// 客户端复用缓存策略
mounted() {
// 如果已有缓存数据,直接使用
if (this.$ssrContext && this.$ssrContext.cachedData) {
this.data = this.$ssrContext.cachedData
}
}
}
13.4 数据预取异常处理
13.4.1 全局错误处理
// 服务端错误处理中间件
const handleError = (err, req, res) => {
console.error(`Error during SSR: ${err.stack}`)
// 区分404和其他错误
if (err.code === 404) {
res.status(404).send('404 | Page Not Found')
} else {
// 生产环境显示友好错误页
if (process.env.NODE_ENV === 'production') {
res.status(500).send('500 | Internal Server Error')
} else {
// 开发环境显示错误堆栈
res.status(500).send(err.stack)
}
}
}
// 在渲染流程中捕获错误
renderer.renderToString(context, (err, html) => {
if (err) {
return handleError(err, req, res)
}
// ...正常处理
})
13.4.2 组件级错误边界
// components/ErrorBoundary.vue
export default {
data: () => ({ error: null }),
errorCaptured(err, vm, info) {
this.error = err
// 服务端记录错误日志
if (process.server) {
serverLogError(err)
}
return false // 阻止错误继续传播
},
render(h) {
return this.error
? h('div', ['Error: ', this.error.message])
: this.$slots.default[0]
}
}
// 使用示例
<template>
<error-boundary>
<unstable-component/>
</error-boundary>
</template>
本章重点总结:
- 分页策略:结合路由参数与Vuex实现动态数据加载
- 认证集成:服务端与客户端统一的认证状态管理
- 缓存优化:多级缓存策略提升服务端性能
- 错误处理:全局与组件级的错误防御机制
最佳实践提示:
- 使用
process.server
和process.client
区分环境 - 为缓存设置合理的TTL和容量限制
- 敏感数据避免直接存储在Vuex state中
- 使用
<client-only>
包裹浏览器端特有功能 - 定期清理无效缓存项
// 客户端专用组件示例
<template>
<div>
<client-only>
<!-- 依赖浏览器API的功能 -->
<calendar-widget/>
</client-only>
</div>
</template>