日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

Vue项目SSR改造实战

發布時間:2025/3/20 vue 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Vue项目SSR改造实战 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
博客已全站升級到https,如果遇到無法訪問,請手動加上https://前綴

我們先看“療效”,你可以打開我的博客u3xyz.com,通過查看源代碼來看SSR直出效果。我的博客已經快上線一年了,但不吹不黑,訪問量非常地小,我也一直在想辦法提升訪問量(包括在sf寫文章,哈哈)。當然,在PC端,搜索引擎一直都是一個重要的流量來源。這里就不得不提到SEO。下圖是我的博客以前在百度的快照:

細心的朋友會發現,這個快照非常簡單,簡單到幾乎什么都沒有。這也是沒辦法的事,博客是基于Vue的SPA頁面,整個項目本來就是一個“空架子”,這個快照從博客2月份上線以來就一直是上面的樣子,直到最近上線SSR。搜索引擎蜘蛛每次來抓取你的網站都是一個樣子,慢慢得,它也就不會來了,相應的,網站的權重,排名肯定不會好。到目前為此,我的博客不用網址進行搜索都搜不到。在上線了SSR后,再加上一些SEO優化,百度快照終于更新了:

為什么要做SSR

文章開始基本已經回答了為什么要做SSR這個問題,當然,還有另一個原因是SSR概念現在在前端非?;?#xff0c;無奈在實際項目中沒有機會,也只有拿博客來練手了。下面將詳細介紹本博客項目SSR全過程。

SSR改造實戰

總的來說SSR改造還是相當容易的。推薦在動手之前,先了解官方文檔和官方Vue SSR Demo,這會讓我們事半功倍。

1. 構建改造

上圖是Vue官方的SSR原理介紹圖片。從這張圖片,我們可以知道:我們需要通過Webpack打包生成兩份bundle文件:

  • Client Bundle,給瀏覽器用。和純Vue前端項目Bundle類似
  • Server Bundle,供服務端SSR使用,一個json文件

不管你項目先前是什么樣子,是否是使用vue-cli生成的。都會有這個構建改造過程。在構建改造這里會用到 vue-server-renderer 庫,這里要注意的是 vue-server-renderer 版本要與Vue版本一樣。下圖是我的構建文件目錄:

  • util.js 提供一些公共方法
  • webpack.base.js是公共的配置
  • webpack.client.js 是生成Client Bundle的配置。核心配置如下:
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')// ...const config = merge(baseConfig, {target: 'web',entry: './src/entry.client.js',plugins: [new webpack.DefinePlugin({'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),'process.env.VUE_ENV': '"client"'}),new webpack.optimize.CommonsChunkPlugin({name: 'vender',minChunks: 2}),// extract webpack runtime & manifest to avoid vendor chunk hash changing// on every build.new webpack.optimize.CommonsChunkPlugin({name: 'manifest'}),new VueSSRClientPlugin()] })
  • webpack.server.js 是生成Server Bundle的配置,核心配置如下:
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')// ...const config = merge(baseConfig, {target: 'node',devtool: '#source-map',entry: './src/entry.server.js',output: {libraryTarget: 'commonjs2',filename: 'server-bundle.js'},externals: nodeExternals({// do not externalize CSS files in case we need to import it from a depwhitelist: /\.css$/}),plugins: [new webpack.DefinePlugin({'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),'process.env.VUE_ENV': '"server"'}),new VueSSRServerPlugin()] })

2. 代碼改造

2.1 必須使用VueRouter, Vuex。ajax庫建議使用axios

可能你的項目沒有使用VueRouter或Vuex。但遺憾的是,Vue-SSR必須基于 Vue + VueRouter + Vuex。Vuex官方沒有提,但其實文檔和Demo都是基于Vuex。我的博客以前也沒有用Vuex,但經過一翻折騰后,還是乖乖加上了Vuex。另外,因為代碼要能同時在瀏覽器和Node.js環境中運行,所以ajax庫建議使用axios這樣的跨平臺庫。

2.2 兩個打包入口(entry),重構app, store, router, 為每個對象增加工廠方法createXXX

每個用戶通過瀏覽器訪問Vue頁面時,都是一個全新的上下文,但在服務端,應用啟動后就一直運行著,處理每個用戶請求的都是在同一個應用上下文中。為了不串數據,需要為每次SSR請求,創建全新的app, store, router

上圖是我的項目文件目錄。

  • app.js, 通用的啟動Vue應用代碼
  • App.vue,Vue應用根組件
  • entry.client.js,瀏覽器環境入口
  • entry.server.js,服務器環境入口
  • index.html,html模板

再看一下具體實現的核心代碼:

// app.jsimport Vue from 'vue' import App from './App.vue' // 根組件 import {createRouter} from './routers/index' import {createStore} from './vuex/store' import {sync} from 'vuex-router-sync' // 把當VueRouter狀態同步到Vuex中// createApp工廠方法 export function createApp (ssrContext) {let router = createRouter() // 創建全新router實例let store = createStore() // 創建全新store實例// 同步路由狀態到store中sync(store, router)// 創建Vue應用const app = new Vue({router,store,ssrContext,render: h => h(App)})return {app, router, store} } // entry.client.js import Vue from 'vue' import { createApp } from './app'const { app, router, store } = createApp()// 如果有__INITIAL_STATE__變量,則將store的狀態用它替換 if (window.__INITIAL_STATE__) {store.replaceState(window.__INITIAL_STATE__) }router.onReady(() => {// 通過路由勾子,執行拉取數據邏輯router.beforeResolve((to, from, next) => {// 找到增量組件,拉取數據 const matched = router.getMatchedComponents(to) const prevMatched = router.getMatchedComponents(from) let diffed = falseconst activated = matched.filter((c, i) => {return diffed || (diffed = (prevMatched[i] !== c))})// 組件數據通過執行asyncData方法獲取const asyncDataHooks = activated.map(c => c.asyncData).filter(_ => _)if (!asyncDataHooks.length) {return next()}// 要注意asyncData方法要返回promise,asyncData調用的vuex action也必須返回promisePromise.all(asyncDataHooks.map(hook => hook({ store, route: to }))).then(() => {next()}).catch(next)})// 將Vue實例掛載到dom中,完成瀏覽器端應用啟動app.$mount('#app') }) // entry.server.js import { createApp } from './app'export default context => {return new Promise((resolve, reject) => {const { app, router, store } = createApp(context)// 設置路由router.push(context.url)router.onReady(() => {const matchedComponents = router.getMatchedComponents()if (!matchedComponents.length) {return reject({ code: 404 })}// 執行asyncData方法,預拉取數據Promise.all(matchedComponents.map(Component => {if (Component.asyncData) {return Component.asyncData({store: store,route: router.currentRoute})}})).then(() => {// 將store的快照掛到ssr上下文上context.state = store.stateresolve(app)}).catch(reject)}, reject)}) } // createStoreimport Vue from 'vue' import Vuex from 'vuex' // ...Vue.use(Vuex)// createStore工廠方法 export function createStore () {return new Vuex.Store({// rootstatestate: {appName: 'appName',title: 'home'},modules: {// ...},strict: process.env.NODE_ENV !== 'production' // 線上環境關閉store檢查}) } // createRouterimport Vue from 'vue' import Router from 'vue-router' Vue.use(Router)// createRouter工廠方法 export function createRouter () {return new Router({mode: 'history', // 注意這里要使用history模式,因為hash不會發送到服務端fallback: false,routes: [{path: '/index',name: 'index',component: () => System.import('./index/index.vue') // 代碼分片},{path: '/detail/:aid',name: 'detail',component: () => System.import('./detail/detail.vue')},// ...{path: '/',redirect: '/index'}]}) }

3. 重構組件獲取數據方式

關于狀態管理,要嚴格遵守Redux思想。建議把應用所有狀態都存于store中,組件使用時再mapState下來,狀態更改嚴格使用action的方式。另一個要提一點的是,action要返回promise。這樣我們就可以使用asyncData方法獲取組件數據了

const actions = {getArticleList ({state, commit}, curPageNum) {commit(FETCH_ARTICLE_LIST, curPageNum)// action 要返回promisereturn apis.getArticleList({data: {size: state.pagi.itemsPerPage,page: curPageNum}}).then((res) => {// ...})} }// 組件asyncData實現 export default {asyncData ({ store }) {return store.dispatch('getArticleList', 1)} }

3. SSR服務器實現

在完成構建和代碼改造后,如果一切順利。我們能得到下面的打包文件:

這時,我們可以開始實現SSR服務端代碼了。下面是我博客SSR實現(基于Koa)

// server.js const Koa = require('koa') const path = require('path') const logger = require('./logger') const server = new Koa() const { createBundleRenderer } = require('vue-server-renderer') const templateHtml = require('fs').readFileSync(path.resolve(__dirname, './index.template.html'), 'utf-8')let distPath = './dist'const renderer = createBundleRenderer(require(`${distPath}/vue-ssr-server-bundle.json`), { runInNewContext: false,template: templateHtml, clientManifest: require(`${distPath}/vue-ssr-client-manifest.json`) })server.use(function * (next) {let ctx = thisconst context = { url: ctx.req.url, pageTitle: 'default-title' }// cgi請求,前端資源請求不能轉到這里來。這里可以通過nginx做if (/\.\w+$/.test(context.url)) {return yield next}// 注意這里也必須返回promise return new Promise((resolve, reject) => {renderer.renderToString(context, function (err, html) {if (err) {logger.error(`[error][ssr-error]: ` + err.stack)return reject(err)}ctx.status = 200ctx.type = 'text/html; charset=utf-8'ctx.body = htmlresolve(html)})}) })// 錯誤處理 server.on('error', function (err) {logger.error('[error][server-error]: ' + err.stack) })let port = 80server.listen(port, () => {logger.info(`[info]: server is deploy on port: ${port}`) })

4. 服務器部署

服務器部署,跟你的項目架構有關。比如我的博客項目在服務端有2個后端服務,一個數據庫服務,nginx用于請求轉發:

5. 遇到的問題及解決辦法

加載不到組件的JS文件 [vue-router] Failed to resolve async component default: Error: Cannot find module 'js\main1.js' [vue-router] uncaught error during route navigation:

解決辦法:

去掉webpack配置中的output.chunkFilename: getFileName('js/main[name]-$hash.js')

if you are using CommonsChunkPlugin, make sure to use it only in the client config because the server bundle requires a single entry chunk.

所以對webpack.server.js不要對配置CommonsChunkPlugin,也不要設置output.chunkFilename

代碼高亮codeMirror使用到navigator對象,只能在瀏覽器環境運行

把執行邏輯放到mounted回調中。實現不行,就封裝一個異步組件,把組件的初始化放到mounted中:

mounted () {let paragraph = require('./paragraph.vue')Vue.component('paragraph', paragraph)new Vue().$mount('#paragraph') }, 串數據

dispatch的action沒有返回promise,保證返回promise即可

路由跳轉

路由跳轉使用router方法或<router-link />標簽,這兩種方式能自適應瀏覽器端和服務端,不要使用a標簽

小結

本文主要記錄了我的博客u3xyz.comSSR過程:

  • 構建webpack改造
  • 代碼改造
  • server端SSR實現
  • 上線部署

最后希望文章能對大家有些許幫助!

愿文地址:Vue項目SSR改造實戰

總結

以上是生活随笔為你收集整理的Vue项目SSR改造实战的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。