# 預渲染
## 預渲染簡介
SEO和首屏加載速度慢的問題,社區討論最多的解決方案是同構 SSR,即首屏使用服務端渲染,之后的交互邏輯交給客戶端處理,解決了單頁應用帶來的兩個問題,但是也帶來了服務器壓力增大,學習成本高,對老項目入侵性過強等問題。
## 版本信息
vue: 2.5.2
webpack: 3.6.0
vue-router: 3.0.1
prerender-spa-plugin: 3.4.0
## 使用
這里我們按照官方 github 的例子敲一下
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
module.exports = {
plugins: [
...
new PrerenderSPAPlugin({
// 這里選擇文件生成目錄.
staticDir: path.join(__dirname, 'dist'),
// 這里選擇你要預加載的 router 路徑,要與在 router 文件里面定義保持一致
routes: [ '/login' ],
})
]
}
還有一些注意事項,假設你的 vue 工程是 vue-cli 2-x 版本的
在 `config/index.js` 里面 `build/assetsPublicPath` 的值為 '/', 這里不能使用相對路徑了
const path = require("path")
module.exports = {
build: {
index: path.resolve(__dirname, "../base/index.html"),
assetsRoot: path.resolve(__dirname, "../dist"),
assetsSubDirectory: "static",
assetsPublicPath: "/",
}
}
還有個配置要注意下在`build/utils.js` 中的 `ExtractTextPlugin.extract` 的 `publicPath` ,否則一些vue中引用的資源會找不到
ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader',
// publicPath: '../../'
})
然后在 `router/index.js` 的配置,預渲染要求是 history 模式,如果不聲明,則生成的頁面都是同一個 html,
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [...]
})
這個時候可以執行 `npm run build` 打包我們的項目了,一切正常的話,dist目錄應該是這個樣子的
├── index.html
├── login
│ └── index.html
└── static
├── css
├── fonts
├── images
├── img
└── js
看到 login 文件夾里面有 index.html,就代表你成功了,
## nginx 配置
這里不需要對這個插件做特殊處理,但是需要對 history 路由做處理,這里貼一下我的ngnix.config配置
server{
listen 8000;
server_name localhost;
root /dist;
error_page 500 502 503 504 /50x.html;
location ~^/declaring/ {
try_files $uri $uri/ /index.html;
}
location = /50x.html {
root /dist;
}
}
到這里,重啟 nginx, 應該就可以看到效果了
## 遇到的問題:
**生成的 html 里面不是我們想要的頁面 html,或者里面的css,js引用失效。**
檢查 `config/index` 里面的 `build/assetsPublicPath` 是不是根目錄,
檢查 路由是否是 history 模式
檢查 `build/utils.js` 中的 `ExtractTextPlugin.extract` 的 `publicPath` 字段是否為空,
**加載頁面會有閃屏的效果**
因為預加載是把當前頁面,提到構建的時候加載,請求的時候,就會先加載html,然后加載 vue 實例,當 vue 加載好了,vue 會 render 並 push 到 #app 的 DOM 節點上,效果就是閃屏,而且如果頁面是動態的,千人千面的,那么用戶第一次看到的頁面將會是你 build 時獲取的數據,然后閃一下,vue 接管頁面后正常渲染,
這個問題,RRS也是遇到,vue 也提供了相應的解決方案,我們移植過來就可以解決了,想了解更詳細的,請參考[客戶端激活(client-side hydration)](https://ssr.vuejs.org/zh/guide/hydration.html)
postProcess(context) {
context.html = context.html.replace('id="app"', 'id="app" data-server-rendered="true"');
return context;
},
下面是全部更改:
// build/webpack.prod.conf.js
...
const PrerenderSpaPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSpaPlugin.PuppeteerRenderer
...
plugins: [
new PrerenderSpaPlugin({
// 編譯后html需要存放的路徑
staticDir: config.build.assetsRoot,
outputDir: config.build.assetsRoot,
indexPath: config.build.index,
// 列出需要預渲染的路由
routes: ['/', '/login'],
postProcess(context) {
context.html = context.html.replace('id="app"', 'id="app" data-server-rendered="true"');
return context;
},
renderer: new Renderer({
headless: false,
renderAfterTime: 5000,
// renderAfterDocumentEvent: 'render-event' // document.dispatchEvent(new Event('render-event'))
})
})
]
// build/utils
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader',
// publicPath: '../../'
})
}
// router/indes.js
let router = new Router({
mode: 'history',
routes: [...]
})
// config/index.js
build: {
...
assetsPublicPath: '/'
}
// nginx.conf
server{
listen 8000;
server_name localhost;
root /dist;
error_page 500 502 503 504 /50x.html;
location /api/ {
#rewrite ^/api/(.*)$ /$1 break;
proxy_pass xxx.xxx.xxx;
}
location ~^/declaring/ {
try_files $uri $uri/ /index.html;
}
location = /50x.html {
root /dist;
}
}