vuecli項(xiàng)目構(gòu)建SSR服務(wù)端渲染的實(shí)現(xiàn)
服務(wù)端渲染(SSR)
將一個(gè) Vue 組件在服務(wù)端渲染成 HTML 字符串并發(fā)送到瀏覽器,最后將這些靜態(tài)標(biāo)記“激活”為可交互應(yīng)用程序的過程就叫服務(wù)端渲染(SSR)
服務(wù)器渲染的 Vue.js 應(yīng)用程序也可以被認(rèn)為是"同構(gòu)"或"通用",因?yàn)閼?yīng)用程序的大部分代碼都可以在服務(wù)器和客戶端上運(yùn)行
為什么使用 服務(wù)端渲染(SSR)
- 更好的 SEO:傳統(tǒng)的 spa 頁面數(shù)據(jù)都是異步加載,搜索引擎爬蟲無法抓取,服務(wù)端渲染(SSR)使搜索引擎爬蟲抓取工具可以直接查看完全渲染的頁面,解決 vue 項(xiàng)目的 seo 問題
- 更快的內(nèi)容到達(dá)時(shí)間 (首屏加載更快):請(qǐng)求頁面時(shí),服務(wù)端將渲染好的頁面直接發(fā)送給瀏覽器進(jìn)行渲染,瀏覽器只需要解析渲染 HTML,無需等待所有的 JavaScript 都完成下載并執(zhí)行,才顯示服務(wù)器渲染的標(biāo)記
服務(wù)端渲染(SSR)缺點(diǎn)
- 開發(fā)條件所限:瀏覽器特定的代碼,只能在某些生命周期鉤子函數(shù)中使用;一些外部擴(kuò)展庫可能需要特殊處理,才能在服務(wù)器渲染應(yīng)用程序中運(yùn)行
- 涉及構(gòu)建設(shè)置和部署的更多要求:與可以部署在任何靜態(tài)文件服務(wù)器上的完全靜態(tài)單頁面應(yīng)用程序 (SPA) 不同,服務(wù)器渲染應(yīng)用程序,需要處于 Node.js server 運(yùn)行環(huán)境
- 更多的服務(wù)器端負(fù)載:在 Node.js 中渲染完整的應(yīng)用程序,顯然會(huì)比僅僅提供靜態(tài)文件的 server 更加大量占用 CPU 資源,因此如果你預(yù)料在高流量環(huán)境下使用,需要準(zhǔn)備相應(yīng)的服務(wù)器負(fù)載,并采用緩存策略
服務(wù)端渲染(SSR)vs 預(yù)渲染(Prerendering)
如果你只是想改善少數(shù)營(yíng)銷頁面(例如 /, /about, /contact 等)的 SEO,那么你可能需要預(yù)渲染,無需使用 web 服務(wù)器實(shí)時(shí)動(dòng)態(tài)編譯 HTML,而是使用預(yù)渲染方式,在構(gòu)建時(shí)簡(jiǎn)單地生成針對(duì)特定路由的靜態(tài) HTML 文件,優(yōu)點(diǎn)是設(shè)置預(yù)渲染更簡(jiǎn)單,并可以將你的前端作為一個(gè)完全靜態(tài)的站點(diǎn)
如果你使用 webpack,你可以使用 prerender-spa-plugin
(npm地址) 插件輕松地添加預(yù)渲染
服務(wù)端渲染(SSR)原理
構(gòu)建流程:所有的文件擁有一個(gè)公共入口 app.js
,進(jìn)入服務(wù)端入口 entry-server.js
和客戶端入口 entry-client.js
,項(xiàng)目完成后通過使用 webpack 打包生成服務(wù)端 server bundle
(一個(gè)供服務(wù)端 SSR 使用的 json 文件)和客戶端 client bundle
(用于瀏覽器),當(dāng)請(qǐng)求頁面時(shí),服務(wù)端將 vue 組件組裝成 HTML 字符串發(fā)送到瀏覽器,混入到客戶端訪問的 HTML 模板中,完成頁面渲染
通過 vuecli 創(chuàng)建 vue 項(xiàng)目
vue create vue-ssr-demo
vue-server-renderer
vue-server-renderer
是 SSR 渲染的核心,提供 createRenderer
方法,這個(gè)方法的 renderToString
可以把 app 渲染成字符串。createBundleRenderer
方法可以通過預(yù)打包應(yīng)用程序代碼創(chuàng)建 bundleRenderer 實(shí)例,來渲染 bundle 和 HTML 模板
安裝 vue-server-renderer
npm install vue-server-renderer --save
注意:
- vue-server-renderer 和 vue 必須匹配版本
- vue-server-renderer 依賴一些 Node.js 原生模塊,因此只能在 Node.js 中使用
避免狀態(tài)單例
Node.js 服務(wù)器是一個(gè)長(zhǎng)期運(yùn)行的進(jìn)程,當(dāng)我們的代碼進(jìn)入該進(jìn)程時(shí),它將進(jìn)行一次取值并留存在內(nèi)存中,這意味著如果創(chuàng)建一個(gè)單例對(duì)象,它將在每個(gè)傳入的請(qǐng)求之間共享,所以我們應(yīng)該暴露一個(gè)可以重復(fù)執(zhí)行的工廠函數(shù),為每個(gè)請(qǐng)求創(chuàng)建一個(gè)新的根 Vue 實(shí)例,如果我們?cè)诙鄠€(gè)請(qǐng)求之間使用一個(gè)共享的實(shí)例,很容易導(dǎo)致交叉請(qǐng)求狀態(tài)污染(同樣的規(guī)則也適用于 router、store 和 event bus 實(shí)例)
創(chuàng)建 路由 router
安裝 vue-router
npm install vue-router --save
在 src
目錄下創(chuàng)建 router
文件夾和 index.js
在 components 目錄下創(chuàng)建 Home.vue 和 About.vue 頁面(根據(jù)項(xiàng)目需求自定義創(chuàng)建)
router/index.js:
import Vue from "vue" import Router from "vue-router" import Home from "@/components/Home" import About from "@/components/About" Vue.use(Router) //每次用戶請(qǐng)求都需要?jiǎng)?chuàng)建一個(gè)新的router實(shí)例 //創(chuàng)建createRouter工廠函數(shù) export default function createRouter() { //創(chuàng)建router實(shí)例 return new Router({ mode: "history", routes: [ { path: "/", name: 'home', component: Home }, { path: "/about", name: 'about', component: About } ] }) }
修改 App.vue
修改 App.vue 頁面,進(jìn)行頁面布局(根據(jù)項(xiàng)目需求自定義布局)
App.vue:
<template> <div id="app"> <nav> <router-link to="/">首頁</router-link> <router-link to="/about">關(guān)于</router-link> </nav> <router-view></router-view> </div> </template>
創(chuàng)建 公共入口 app.js
在 src
目錄下創(chuàng)建 公共入口 app.js
,用于創(chuàng)建 vue 實(shí)例
app.js:
import Vue from "vue" import App from "./App.vue" import createRouter from "./router" //創(chuàng)建createApp工廠函數(shù) export default function createApp() { const router = createRouter() //創(chuàng)建vue實(shí)例 const app = new Vue({ router, render: h => h(App), }) return { app, router } }
創(chuàng)建 服務(wù)端入口 entry-server.js
在 src
目錄下創(chuàng)建 服務(wù)端入口 entry-server.js
,用于渲染首屏
entry-server.js:
import createApp from "./app" export default context => { return new Promise((resolve, reject) => { const { app, router } = createApp() //渲染首屏 router.push(context.url) router.onReady(() => { resolve(app) }, reject) }) }
創(chuàng)建 客戶端入口 entry-client.js
在 src
目錄下創(chuàng)建 客戶端入口 entry-client.js
,用于掛載激活 app
entry-client.js:
import createApp from "./app" const { app, router } = createApp() router.onReady(() => { //掛載激活app app.$mount("#app") })
創(chuàng)建 頁面模板 index.temp.html
在 public
目錄下創(chuàng)建 index.temp.html
,作為渲染 Vue 應(yīng)用程序時(shí),renderer 生成 HTML 頁面包裹容器,來包裹生成的 HTML 標(biāo)記
<!--vue-ssr-outlet-->
注釋將是應(yīng)用程序 HTML 標(biāo)記注入的地方
index.temp.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vue ssr</title> </head> <body> <!--vue-ssr-outlet--> </body> </html>
創(chuàng)建 Node.js 服務(wù)器
服務(wù)端渲染(SSR)需要使用 Node.js 服務(wù)器,這里使用 express
框架搭建
安裝 express
npm install express --save
根目錄下創(chuàng)建 server.js
文件,用于搭建 Node.js 服務(wù)器
server.js:
//nodejs服務(wù)器 const express = require("express") const Vue = require("vue") const fs = require("fs") //創(chuàng)建express實(shí)例 const app = express() //創(chuàng)建渲染器 const { createBundleRenderer } = require("vue-server-renderer") const serverBundle = require("./dist/server/vue-ssr-server-bundle.json") const clientManifest = require("./dist/client/vue-ssr-client-manifest.json") const renderer = createBundleRenderer(serverBundle, { runInNewContext: false, template: fs.readFileSync("./public/index.temp.html", "utf-8"), //頁面模板 clientManifest }) //中間件處理靜態(tài)文件請(qǐng)求 app.use(express.static("./dist/client", {index: false})) //將路由的處理交給vue app.get("*", async (req, res) => { try { const context = { url: req.url, title: "" } const html = await renderer.renderToString(context) res.send(html) }catch { res.status(500).send("服務(wù)器內(nèi)部錯(cuò)誤!") } }) app.listen(9999, () => { console.log("服務(wù)器渲染成功!") })
webpack 打包配置
根目錄下創(chuàng)建 vue 配置文件 vue.config.js
進(jìn)行 webpack 配置,該配置會(huì)覆蓋 vue-cli 中 webpack 的默認(rèn)配置
vue.config.js:
const VueSSRServerPlugin = require("vue-server-renderer/server-plugin") const VueSSRClientPlugin = require("vue-server-renderer/client-plugin") //環(huán)境變量,決定入口是客戶端還是服務(wù)端 const TARGRT_NODE = process.env.WEBPACK_TARGET === "node" const target = TARGRT_NODE ? "server" : "client" module.exports = { css: { extract: false }, outputDir: "./dist/" + target, configureWebpack: () => ({ //將 entry 指向應(yīng)用程序的 server entry 文件 entry: `./src/entry-${target}.js`, //對(duì) bundle renderer 提供 source map 支持 devtool: "source-map", //這允許 webpack 以 Node 適用方式(Node-appropriate fashion)處理動(dòng)態(tài)導(dǎo)入(dynamic import) //并且還會(huì)在編譯 Vue 組件時(shí),告知 `vue-loader` 輸送面向服務(wù)器代碼(server-oriented code) target: TARGRT_NODE ? "node" : "web", node: TARGRT_NODE ? undefined : false, output: { //此處告知 server bundle 使用 Node 風(fēng)格導(dǎo)出模塊(Node-style exports) libraryTarget: TARGRT_NODE ? "commonjs2" : undefined }, optimization: { splitChunks: TARGRT_NODE ? false : undefined }, //將服務(wù)器的整個(gè)輸出構(gòu)建為單個(gè) JOSN 文件的插件 //服務(wù)端默認(rèn)文件名為 vue-ssr-server-bundle.json plugins: [TARGRT_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()] }) }
打包腳本配置
cross-env
插件:運(yùn)行跨平臺(tái)設(shè)置和使用環(huán)境變量的腳本
安裝 cross-env 插件
npm install cross-env --save
在 package.json
文件中定義項(xiàng)目運(yùn)行打包腳本
package.json:
{ ...... "scripts": { "server": "node server", "build:client": "vue-cli-service build", "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server", "build": "npm run build:server && npm run build:client" }, ...... }
終端執(zhí)行命令打包項(xiàng)目:
npm run build
打包完成后,終端執(zhí)行命令啟動(dòng) Node.js 服務(wù)器
npm run server
服務(wù)器啟動(dòng)后,瀏覽器打開 localhost:9999 即可訪問 SSR 項(xiàng)目
查看網(wǎng)頁源代碼發(fā)現(xiàn)根元素上添加了一個(gè)特殊的屬性:data-server-rendered
,該屬性讓客戶端 Vue 知道這部分 HTML 是由 Vue 在服務(wù)端渲染的,并且應(yīng)該以激活模式進(jìn)行掛載
<div id="app" data-server-rendered="true">......</div>
項(xiàng)目目錄:
打包后 dist 目錄:
到此這篇關(guān)于vuecli項(xiàng)目構(gòu)建SSR服務(wù)端渲染的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)vuecli構(gòu)建SSR服務(wù)端渲染內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Egg Vue SSR 服務(wù)端渲染數(shù)據(jù)請(qǐng)求與asyncData
- 15分鐘學(xué)會(huì)vue項(xiàng)目改造成SSR(小白教程)
- vue ssr+koa2構(gòu)建服務(wù)端渲染的示例代碼
- Vue SSR 即時(shí)編譯技術(shù)的實(shí)現(xiàn)
- Vue使用預(yù)渲染代替SSR的方法
- 如何構(gòu)建 vue-ssr 項(xiàng)目的方法步驟
- vue的ssr服務(wù)端渲染示例詳解
- vue中vue-router的使用說明(包括在ssr中的使用)
- 關(guān)于VueSSR的一些理解和詳細(xì)配置
- Vue.js?狀態(tài)管理及?SSR解析
相關(guān)文章
如何在vue項(xiàng)目中嵌入jsp頁面的方法(2種)
這篇文章主要介紹了如何在vue項(xiàng)目中嵌入jsp頁面的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02基于element-ui封裝可搜索的懶加載tree組件的實(shí)現(xiàn)
這篇文章主要介紹了基于element-ui封裝可搜索的懶加載tree組件的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05基于Vue和Firebase實(shí)現(xiàn)一個(gè)實(shí)時(shí)聊天應(yīng)用
在本文中,我們將學(xué)習(xí)如何使用Vue.js和Firebase來構(gòu)建一個(gè)實(shí)時(shí)聊天應(yīng)用,Vue.js是一種流行的JavaScript前端框架,而Firebase是Google提供的實(shí)時(shí)數(shù)據(jù)庫和后端服務(wù),結(jié)合這兩者,我們可以快速搭建一個(gè)功能強(qiáng)大的實(shí)時(shí)聊天應(yīng)用,需要的朋友可以參考下2023-08-08Vue項(xiàng)目的表單校驗(yàn)實(shí)戰(zhàn)指南
這篇文章主要介紹了Vue項(xiàng)目表單校驗(yàn)的相關(guān)資料,前端表單校驗(yàn)?zāi)軠p少無效請(qǐng)求,保護(hù)后端接口,使用ElementPlus表單組件進(jìn)行校驗(yàn),需要準(zhǔn)備表單對(duì)象、規(guī)則對(duì)象并進(jìn)行雙向綁定,用戶名、密碼以及協(xié)議勾選等字段都需符合特定規(guī)則,需要的朋友可以參考下2024-10-10詳解vue-cli 本地開發(fā)mock數(shù)據(jù)使用方法
這篇文章主要介紹了詳解vue-cli 本地開發(fā)mock數(shù)據(jù)使用方法,如果后端接口尚未開發(fā)完成,前端開發(fā)一般使用mock數(shù)據(jù)。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05vue給input file綁定函數(shù)獲取當(dāng)前上傳的對(duì)象完美實(shí)現(xiàn)方法
這篇文章主要介紹了vue給input file綁定函數(shù)獲取當(dāng)前上傳的對(duì)象完美實(shí)現(xiàn)方法,需要的朋友可以參考下2017-12-12如何用Vite構(gòu)建工具快速創(chuàng)建Vue項(xiàng)目
Vite是一個(gè)web開發(fā)構(gòu)建工具,由于其原生?ES?模塊導(dǎo)入方法,它允許快速提供代碼,下面這篇文章主要給大家介紹了關(guān)于如何用Vite構(gòu)建工具快速創(chuàng)建Vue項(xiàng)目的相關(guān)資料,需要的朋友可以參考下2022-05-05