傳統的瀏覽器端渲染
通常,我們的Vue項目實在npm run build打包之後,直接放到服務器端。瀏覽器去請求相應的html,加載對應的js文件,生成DOM。
路由改變,局部刷新,瀏覽器不會刷新
缺點
需要js全部加載完,頁面才能出來,加載較慢(懶加載)
js改變dom,生成頁面,不利於SEO
SSR(服務器端渲染是什麼)
SSR的原理是將打包後的文件,先在服務器端處理,生成一個個的HTML字符串,當瀏覽器請求的時候直接發過去。
每一個路由請求到的都是一個html串,路由改變,瀏覽器刷新
優點
利於SEO,搜索引擎爬蟲抓取工具可以直接查看完全渲染的頁面
更快的內容到達時間 (time-to-content),更好的用戶體驗,特別是對於緩慢的網絡情況或運行緩慢的設備。
預渲染
只是用來改善少數營銷頁面的 SEO,可以使用預渲染,在構建時 (build time) 簡單地生成針對特定路由的靜態 HTML 文件,而無需使用 web 服務器實時動態編譯 HTML。
vue項目中手動加入SSR
1、創建兩個js文件,server.js和client.js
將之前的一次打包,轉化爲兩次打包,一個用於服務端打包入口js文件,一個用於客戶端打包入口js文件。
server.js
//服務器端打包入口文件
import {createApp} from './main'
//根據返回的內容,拿到指定的路由節點
export default context => {
return new Promise((resolve, reject)=>{
const {app, router} = createApp();
router.push(context.url);
router.onReady(()=>{
const mathComponents = router.getMatchedComponents();
if(!mathComponents){
return reject({code: 404});
}
resolve(app);
},reject);
})
}
client.js
//客戶端打包入口文件
import {createApp} from './main'
const {app,router} = createApp();
//路由完成之後,再去進行掛載,以防有異步路由的情況
router.onReady(()=>{
app.$mount("#app");
})
2、新建存放服務器端生成的html模板
index.ssr.html用於保存服務器端生成的html,其中爲注入標記,即在這兒進行添加。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>ssr-test</title>
</head>
<body>
<!--vue-ssr-outlet-->
<script type="text/javascript" src="<%=htmlWebpackPlugin.options.files.js%>"></script>
</body>
</html>
3、改造router和main文件,將其中的new Router和new Vue轉化爲方法,便於後面使用
router:
export function createRouter(){
return new Router({
mode: "history",
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
}
]
})
}
main:
let router = createRouter();
export function createApp(){
const app = new Vue({
router,
render:h => h(App)
})
return {app,router};
}
4、創建客戶端和服務器端打包配置文件
安裝:
npm install vue vue-server-renderer --save
webpack.build.server.js拷貝webpack.prod.conf.js生產打包配置文件進行修改
添加入口文件
打包後的服務端文件需要在node下面執行,所以將target指定爲node
nodejs的規範爲commonjs,所以需要將output中的libraryTarget指定爲commonjs。新版本node使用commonjs2
修改HtmlWebpackPlugin下面的模板文件和目標文件爲我們創建的index.ssr.html
刪除HtmlWebpackPlugin下面minify中的removeComments(刪除註釋),因爲我們上面說的服務器端html模板中的註釋有特定作用,不能被刪除
引入vue-server-renderer下面的server-plugin,並進行註冊
const VueSSRServePlugin = require('vue-server-renderer/server-plugin')
entry:{
app:'./src/server.js'
},
target: 'node',
output:{
libraryTarget:'commonjs2'
},
plugins: [
new VueSSRServePlugin(),
new HtmlWebpackPlugin({
filename: 'index.ssr.html',
template: 'index.ssr.html',
inject: true,
files:{
js:'app.js'
},
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
})
]
webpack.build.client.js拷貝webpack.prod.conf.js生產打包配置文件進行修改
添加入口文件
引入vue-server-renderer下面的client-plugin,並進行註冊
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
entry:{
app:'./src/ client.js'
},
plugins: [
new VueSSRClientPlugin(),
]
5、package.json中新加兩條打包命令
"build:client": "webpack --config build/webpack.build.client.js",
"build:server": "webpack --config build/webpack.build.server.js"
npm run build:client
npm run build:server
打包完成之後的目錄
6、配置node服務
根目錄創建server.js
const express = require('express');
const server = express();
const { createBundleRenderer } = require('vue-server-renderer');
const path = require('path');
const fs = require('fs');
const { target } = require('./config/dev.env');
const serverBuild = require(path.resolve(__dirname, "./dist/vue-ssr-server-bundle.json"));
const clientManifest = require(path.resolve(__dirname, "./dist/vue-ssr-client-manifest.json"));
const template = fs.readFileSync(path.resolve(__dirname, "./dist/index.ssr.html"), 'utf-8');
const renderer = createBundleRenderer(serverBuild, {
runInNewContext: false,
template: template,
clientManifest: clientManifest
});
server.get("*", (req, res) => {
if(req.url != "/favicon.ico"){
const context = { url: req.url };
const ssrStream = renderer.renderToStream(context);
ssrStream.on('error',(err)=>{
res.status(500).end('Internal Server Error')
console.log(err);
return;
});
let buffers = [];
ssrStream.on('data',(data) => buffers.push(data));
ssrStream.on('end', () => {res.end(Buffer.concat(buffers))});
}
})
server.listen(1000);
7、啓動服務,查看效果
node server.js