vue服务端渲染的实例代码
发布时间 - 2026-01-11 02:59:44 点击率:次一、什么是服务端渲染

客户端请求服务器,服务器根据请求地址获得匹配的组件,在调用匹配到的组件返回Promise (官方是asyncData方法)来将需要的数据拿到。最后再通过window.__initial_state=data将其写入网页,最后将服务端渲染好的网页返回回去。接下来客户端将用新的store状态把原来的store状态替换掉,保证客户端和服务端的数据同步。遇到没被服务端渲染的组件,再去发异步请求拿数据。
服务端渲染的环境搭建
这是vue官网的服务端渲染的示意图,ssr有两个入口文件,分别是客户端的入后文件和服务端的入口文件,webpack通过两个入口文件分别打包成给服务端用的server bundle和给客户端用的client bundle.当服务器接收到了来自客户端的请求之后,会创建一个渲染器bundleRenderer,这个bundleRenderer会读取上面生成的server bundle文件,并且执行它的代码, 然后发送一个生成好的html到浏览器,等到客户端加载了client bundle之后,会和服务端生成的DOM进行Hydration(判断这个DOM和自己即将生成的DOM是否相同,如果相同就将客户端的vue实例挂载到这个DOM上)
实现步骤:
1、创建vue实例(main.js)
importVuefrom'vue'
importAppfrom'./App.vue'
importiViewfrom'iview';
import{createStore}from'./store'
import{createRouter}from'./router'
import{sync}from'vuex-router-sync'
Vue.use(iView);
export functioncreateApp() {
conststore = createStore()
constrouter = createRouter()
sync(store,router)
constapp =newVue({
router,
store,
render: h => h(App)
})
return{app,router,store}
}
因为要做服务端渲染,所以这里不需要再用el去挂载,现将app、router、store导出
2、服务端入口文件(entry-server.js)
import{ createApp }from'./main'
constisDev = process.env.NODE_ENV !=='production'
const{ app,router,store } = createApp()
constgetAllAsyncData=function(component){
letstores = []
functionloopComponent(component) {
if(typeofcomponent.asyncData !=='undefined') {
for(letaofcomponent.asyncData({store,route: router.currentRoute})) {
stores.push(a)
}
}
if(typeofcomponent.components !=='undefined') {
for(letcincomponent.components){
loopComponent(component.components[c])
}
}
}
loopComponent(component)
returnstores
}
export defaultcontext => {
return newPromise((resolve,reject) => {
consts = isDev && Date.now()
const{url} = context
constfullPath = router.resolve(url).route.fullPath
if(fullPath !== url) {
reject({url: fullPath })
}
router.push(url)
router.onReady(() => {
constmatchedComponents = router.getMatchedComponents()
if(!matchedComponents.length) {
reject({code:404})
}
letallAsyncData = getAllAsyncData(matchedComponents[0])
Promise.all(allAsyncData).then(() => {
isDev && console.log(`data pre-fetch:${Date.now() - s}ms`)
context.state = store.state
resolve(app)
}).catch(reject)
},reject)
})
}
这个文件的主要工作是接受从服务端传递过来的context参数,context包含当前页面的url,用getMatchedComponents方法获取当前url下的组件,返回一个数组,遍历这个数组中的组件,如果组件有asyncData钩子函数,则传递store获取数据,最后返回一个promise对象。
store.state的作用是将服务端获取到的数据挂载到context对象上,后面在server.js文件里会把这些数据直接发送到浏览器端与客户端的vue实例进行数据(状态)同步。
3、客户端入口文件(entry-client.js)
importVuefrom'vue'
import'es6-promise/auto'
import{ createApp }from'./main'
importProgressBarfrom'./components/ProgressBar.vue'
// global progress bar
constbar = Vue.prototype.$bar =newVue(ProgressBar).$mount()
document.body.appendChild(bar.$el)
Vue.mixin({
beforeRouteUpdate(to,from,next) {
const{ asyncData } =this.$options
if(asyncData) {
Promise.all(asyncData({
store:this.$store,
route: to
})).then(next).catch(next)
}else{
next()
}
}
})
const{ app,router,store } = createApp()
if(window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
router.onReady(() => {
router.beforeResolve((to,from,next) => {
constmatched = router.getMatchedComponents(to)
constprevMatched = router.getMatchedComponents(from)
letdiffed =false
constactivated = matched.filter((c,i) => {
returndiffed || (diffed = (prevMatched[i] !== c))
})
constasyncDataHooks = activated.map(c => c.asyncData).filter(_ => _)
if(!asyncDataHooks.length) {
returnnext()
}
bar.start()
Promise.all(asyncDataHooks.map(hook => hook({ store,route: to })))
.then(() => {
bar.finish()
next()
})
.catch(next)
})
app.$mount('#app')
})
if('https:'=== location.protocol && navigator.serviceWorker) {
navigator.serviceWorker.register('/service-worker.js')
}
if(window.INITIAL_STATE) {
store.replaceState(window.INITIAL_STATE)
}
这句的作用是如果服务端的vuex数据发生改变,就将客户端的数据替换掉,保证客户端和服务端的数据同步
Service Worker主要用于拦截并修改访问和资源请求,细粒度地缓存资源。它运行浏览器在后台,运行环境与普通页面脚本不同,所以不能直接参与页面交互。出于安全考虑,service worker只能运行在HTTPS上,防止被人从中攻击。
4、创建服务端渲染器(server.js)
constfs = require('fs')
constpath = require('path')
constLRU = require('lru-cache')
constexpress = require('express')
constcompression = require('compression')
constresolve= file => path.resolve(__dirname,file)
const{ createBundleRenderer } = require('vue-server-renderer')
constisProd = process.env.NODE_ENV ==='production'|| process.env.NODE_ENV ==='beta'
constuseMicroCache = process.env.MICRO_CACHE !=='false'
constserverInfo =
`express/${require('express/package.json').version}`+
`vue-server-renderer/${require('vue-server-renderer/package.json').version}`
constapp = express()
consttemplate = fs.readFileSync(resolve('./src/index.template.html'),'utf-8')
functioncreateRenderer(bundle,options) {
returncreateBundleRenderer(bundle,Object.assign(options,{
template,
cache: LRU({
max:1000,
maxAge:1000*60*15
}),
basedir: resolve('./dist'),
runInNewContext:false
}))
}
letrenderer
letreadyPromise
if(isProd) {
constbundle = require('./dist/vue-ssr-server-bundle.json')
constclientManifest = require('./dist/vue-ssr-client-manifest.json')
renderer = createRenderer(bundle,{
clientManifest
})
}else{
readyPromise = require('./build/setup-dev-server')(app,(bundle,options) => {
renderer = createRenderer(bundle,options)
})
}
constserve= (path,cache) => express.static(resolve(path),{
maxAge: cache && isProd ?1000*60*60*24*30:0
})
app.use(compression({threshold:0}))
app.use('/dist',serve('./dist',true))
app.use('/static',serve('./static',true))
app.use('/service-worker.js',serve('./dist/service-worker.js'))
constmicroCache = LRU({
max:100,
maxAge:1000
})
constisCacheable= req => useMicroCache
functionrender(req,res) {
consts = Date.now()
res.setHeader("Content-Type","text/html")
res.setHeader("Server",serverInfo)
consthandleError= err => {
if(err.url) {
res.redirect(err.url)
}else if(err.code ===404) {
res.status(404).end('404 | Page Not Found')
}else{
// Render Error Page or Redirect
res.status(500).end('500 | Internal Server Error')
console.error(`error during render :${req.url}`)
console.error(err.stack)
}
}
constcacheable = isCacheable(req)
if(cacheable) {
consthit = microCache.get(req.url)
if(hit) {
if(!isProd) {
console.log(`cache hit!`)
}
returnres.end(hit)
}
}
constcontext = {
title:'Vue DB',// default title
url: req.url
}
renderer.renderToString(context,(err,html) => {
if(err) {
returnhandleError(err)
}
res.end(html)
if(cacheable) {
microCache.set(req.url,html)
}
if(!isProd) {
console.log(`whole request:${Date.now() - s}ms`)
}
})
}
app.get('*',isProd ? render : (req,res) => {
readyPromise.then(() => render(req,res))
})
constport = process.env.PORT ||8888
app.listen(port,() => {
console.log(`server started at localhost:${port}`)
})
5、客户端api文件create-api-client.js
/**
* Created by lin on 2017/8/25.
*/
import axios from 'axios';
let api;
axios.defaults.baseURL = process.env.API_URL;
axios.defaults.timeout = 10000;
axios.interceptors.response.use((res) => {
if (res.status >= 200 && res.status < 300) {
return res;
}
return Promise.reject(res);
}, (error) => {
return Promise.reject({message: '网络异常,请刷新重试', err: error});
});
if (process.__API__) {
api = process.__API__;
} else {
api = {
get: function(url) {
return new Promise((resolve, reject) => {
axios.get(url).then(res => {
resolve(res);
}).catch((error) => {
reject(error);
});
});
},
post: function(target, options = {}) {
return new Promise((resolve, reject) => {
axios.post(target, options).then(res => {
resolve(res);
}).catch((error) => {
reject(error);
});
});
}
};
}
export default api;
6、服务端api文件create-api-server.js
/**
* Created by lin on 2017/8/25.
*/
import axios from 'axios';
let cook = process.__COOKIE__ || '';
let api;
axios.defaults.baseURL = 'https://api.douban.com/v2/';
axios.defaults.timeout = 10000;
axios.interceptors.response.use((res) => {
if (res.status >= 200 && res.status < 300) {
return Promise.resolve(res);
}
return Promise.reject(res);
}, (error) => {
// 网络异常
return Promise.reject({message: '网络异常,请刷新重试', err: error, type: 1});
});
if (process.__API__) {
api = process.__API__;
} else {
api = {
get: function(target) {
return new Promise((resolve, reject) => {
axios.request({
url: encodeURI(target),
method: 'get',
headers: {
'Cookie': cook
}
}).then(res => {
resolve(res);
}).catch((error) => {
reject(error);
});
});
},
post: function(target, options = {}) {
return new Promise((resolve, reject) => {
axios.request({
url: target,
method: 'post',
headers: {
'Cookie': cook
},
params: options
}).then(res => {
resolve(res);
}).catch((error) => {
reject(error);
});
});
}
};
}
export default api;
六、那些年遇到的那些坑
问题1、window is not defined
答案1:给用到浏览器对象的地方加if (typeof window !== 'undefined') {},有一些插件里也用到了浏览器对象,在使用的地方也加一个条件判断:
if (typeofwindow !== 'undefined') {
Vue.use(VueAnalytics, {
id: process.env.UA_TRACKING_ID,
router
})
}
问题2:用到非Vue系列的插件,如hello.all.js(三方登录的插件),需要用的地方才引用,报的错和问题1一样。
答案2:这个时候不能再用import导入,需要使用require,
let hello
if (typeof window !== 'undefined') {
hello = require('hello')
}
问题3:引用bootstrap
答案3:将bootstrap.css和bootstrap.js加入webpack.base.config.js的entry中的vendor中
问题6:bootstap需要jquery,此时把jQuery加在vendor中没用。
答案6:给webpack.base.config.js的plugins添加一个插件,如:
newwebpack.ProvidePlugin({
$ : "jquery",
jQuery : "jquery",
"window.jQuery" :"jquery"
})
七、例子
https://github.com/linmoer/ssr-vue这是一个服务端渲的例子
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# vue
# 服务器端渲染原理
# 服务器端渲染
# 详解vue服务端渲染(SSR)初探
# 基于vue-ssr服务端渲染入门详解
# 详解如何使用Vue2做服务端渲染
# 详解基于vue的服务端渲染框架NUXT
# 详解vue服务端渲染浏览器端缓存(keep-alive)
# vue ssr服务端渲染(小白解惑)
# vue服务端渲染添加缓存的方法
# vue的ssr服务端渲染示例详解
# Vue3+TypeScript+Vite服务端渲染项目的实现
# 服务端
# 客户端
# 就将
# 再用
# 重试
# 这是
# 数据同步
# 运行环境
# 渲染器
# 不需要
# 被人
# 遍历
# 将其
# 这是一个
# 要做
# 这个时候
# 再去
# 会把
# 发送到
# 后将
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel如何处理CORS跨域请求?(配置示例)
制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?
Win11摄像头无法使用怎么办_Win11相机隐私权限开启教程【详解】
瓜子二手车官方网站在线入口 瓜子二手车网页版官网通道入口
详解MySQL数据库的安装与密码配置
Laravel项目如何进行性能优化_Laravel应用性能分析与优化技巧大全
Laravel中的Facade(门面)到底是什么原理
如何获取上海专业网站定制建站电话?
如何用PHP快速搭建CMS系统?
Bootstrap整体框架之CSS12栅格系统
Laravel如何实现邮件验证激活账户_Laravel内置MustVerifyEmail接口配置【步骤】
如何在建站宝盒中设置产品搜索功能?
html5怎么画眼睛_HT5用Canvas或SVG画眼球瞳孔加JS控制动态【绘制】
怎么用AI帮你为初创公司进行市场定位分析?
html5源代码发行怎么设置权限_访问权限控制方法与实践【指南】
如何使用 jQuery 正确渲染 Instagram 风格的标签列表
Laravel如何处理跨站请求伪造(CSRF)保护_Laravel表单安全机制与令牌校验
高防服务器如何保障网站安全无虞?
Windows Hello人脸识别突然无法使用
如何用AI一键生成爆款短视频文案?小红书AI文案写作指令【教程】
详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)
如何快速登录WAP自助建站平台?
如何用IIS7快速搭建并优化网站站点?
laravel服务容器和依赖注入怎么理解_laravel服务容器与依赖注入解析
Laravel怎么实现观察者模式Observer_Laravel模型事件监听与解耦开发【指南】
浅谈redis在项目中的应用
PHP 实现电台节目表的智能时间匹配与今日/明日轮播逻辑
如何用腾讯建站主机快速创建免费网站?
高配服务器限时抢购:企业级配置与回收服务一站式优惠方案
弹幕视频网站制作教程下载,弹幕视频网站是什么意思?
Laravel如何集成第三方登录_Laravel Socialite实现微信QQ微博登录
活动邀请函制作网站有哪些,活动邀请函文案?
Laravel的HTTP客户端怎么用_Laravel HTTP Client发起API请求教程
Laravel如何操作JSON类型的数据库字段?(Eloquent示例)
Laravel如何实现API资源集合?(Resource Collection教程)
Laravel如何设置定时任务(Cron Job)_Laravel调度器与任务计划配置
Laravel控制器是什么_Laravel MVC架构中Controller的作用与实践
Laravel如何使用Telescope进行调试?(安装和使用教程)
网站制作软件免费下载安装,有哪些免费下载的软件网站?
中山网站推广排名,中山信息港登录入口?
简单实现jsp分页
非常酷的网站设计制作软件,酷培ai教育官方网站?
Python文件操作最佳实践_稳定性说明【指导】
百度浏览器ai对话怎么关 百度浏览器ai聊天窗口隐藏
Laravel如何实现数据导出到PDF_Laravel使用snappy生成网页快照PDF【方案】
通义万相免费版怎么用_通义万相免费版使用方法详细指南【教程】
制作无缝贴图网站有哪些,3dmax无缝贴图怎么调?
Linux安全能力提升路径_长期防护思维说明【指导】
如何快速搭建支持数据库操作的智能建站平台?
Laravel中间件如何使用_Laravel自定义中间件实现权限控制

