Vue 服務端渲染(SSR)詳解

Vue SSR是一種在服務端將 Vue 應用渲染成 HTML 字符串,然后直接發送到客戶端的技術。相比傳統的客戶端渲染,Vue SSR 能帶來更好的 SEO 性能和更快的首屏加載時間。下面我們從零到一,結合項目源碼,詳細講解如何實現一個 Vue SSR 項目。

1. 項目結構

以下以一個基本的Demo來說明服務端渲染的實現,下圖是項目的基本結構:

2.?安裝項目依賴

以下是package.json中的配置:

{"name": "vue-ssr-example","version": "1.0.0","scripts": {"dev": "node server","dev:client": "vite","dev:server": "node server","dev:both": "concurrently \"npm run dev:client\" \"npm run dev:server\"","build": "npm run build:client && npm run build:server","build:client": "vite build --ssrManifest --outDir dist/client","build:server": "vite build --ssr src/entry-server.js --outDir dist/server","serve": "cross-env NODE_ENV=production node server"},"dependencies": {"vue": "^3.5.6","vue-router": "^4.0.0","pinia": "^2.0.0","express": "^4.17.1"},"devDependencies": {"@vitejs/plugin-vue": "^4.0.0","vite": "^4.0.0","cross-env": "^7.0.3","concurrently": "^6.2.0"}
}

3.?配置腳手架

// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";export default defineConfig({plugins: [vue()],build: {minify: false,},
});

4. 服務端渲染流程

4.1.?請求階段

Node服務器接收請求,以下是server代碼:

// server/index.js// 引入必要的模塊
const fs = require("fs");
const path = require("path");
const express = require("express");
const { createServer: createViteServer } = require("vite"); // 重命名Vite的createServer方法// 創建SSR服務器的主函數,接受生產環境標志參數
async function createServer(isProd = process.env.NODE_ENV === "production") {// 創建Express實例const app = express(); let vite;// 開發環境配置if (!isProd) {// 創建Vite開發服務器vite = await createViteServer({server: { middlewareMode: true }, // 中間件模式appType: "custom" // 自定義應用類型(避免Vite的默認SPA處理)});app.use(vite.middlewares); // 使用Vite中間件處理請求} else {// 生產環境直接使用構建好的靜態文件app.use(express.static(path.resolve(__dirname, "../dist/client")));}// 處理所有路由的中間件app.use("*", async (req, res) => {const url = req.originalUrl; // 獲取請求URLtry {let template, render;// 開發環境處理if (!isProd) {// 讀取HTML模板文件template = fs.readFileSync(path.resolve(__dirname, "../index.html"),"utf-8");// 使用Vite轉換HTML模板(包含HMR支持)template = await vite.transformIndexHtml(url, template);// 加載服務端入口模塊render = (await vite.ssrLoadModule("/src/entry-server.js")).render;} else {// 生產環境處理template = fs.readFileSync(path.resolve(__dirname, "../dist/client/index.html"),"utf-8");// 直接加載構建后的服務端入口render = require("../dist/server/entry-server.js").render;}// 調用渲染函數獲取SSR結果const [appHtml, preloadLinks, initialState] = await render(url);// 替換模板中的占位符const html = template.replace(`<!--app-html-->`, appHtml) // 插入應用HTML.replace(`"<!--pinia-state-->"`, JSON.stringify(initialState)); // 序列化Pinia狀態// 返回最終HTMLres.status(200).set({ "Content-Type": "text/html" }).end(html);} catch (e) {// 開發環境下修正錯誤堆棧跟蹤if (!isProd) {vite.ssrFixStacktrace(e);}res.status(500).end(e.message);}});// 啟動服務器const port = process.env.PORT || 3000;app.listen(port, () => {console.log(`Server is running on http://localhost:${port}`);});
}// 啟動服務器
createServer();

4.2.?應用初始化

創建Vue實例,以下是entry-server.js文件的代碼:

// src/entry-server.js
// 從主模塊導入應用創建函數
import { createApp } from "./main";
// 導入Vue服務端渲染工具
import { renderToString } from "vue/server-renderer";// 服務端渲染函數,接收請求URL作為參數
export async function render(url) {// 創建Vue應用實例(包含應用、路由和狀態管理)const { app, router, pinia } = createApp();// 設置當前路由位置await router.push(url);// 等待路由導航完成await router.isReady();// 創建SSR上下文對象(用于收集渲染過程中的資源信息)const context = {};// 將Vue應用渲染為HTML字符串const appHtml = await renderToString(app, context);// 序列化Pinia狀態(用于客戶端hydration)const initialState = JSON.stringify(pinia.state.value);// 返回渲染結果數組:// [0] 應用HTML字符串// [1] 預加載模塊信息(用于資源預加載)// [2] 初始狀態數據return [appHtml, context.modules, initialState];}

以下是上面代碼中引入的main.js文件代碼:

// /src/main.js
// 導入SSR專用Vue應用創建方法和核心模塊
import { createSSRApp } from "vue"; // 服務端渲染專用應用創建方法
import { createRouter } from "./router"; // 自定義路由配置
import { createPinia } from "pinia"; // 狀態管理庫
import App from "./App.vue"; // 根組件// 應用工廠函數(SSR核心要求)
export function createApp() {// 創建SSR應用實例(與客戶端createApp的區別在于SSR優化)const app = createSSRApp(App);// 初始化路由系統const router = createRouter();// 創建Pinia狀態管理實例const pinia = createPinia();// 注冊路由插件(使this.$router可用)app.use(router);// 注冊狀態管理(使this.$pinia可用)app.use(pinia);// 返回應用核心三件套,供entry-server和entry-client使用:// app: Vue應用實例// router: 路由實例(處理服務端/客戶端路由同步)// pinia: 狀態管理實例(保證服務端/客戶端狀態一致)return { app, router, pinia };
}

以下是根組件App.vue代碼:

<template><div><nav><router-link to="/">Home</router-link> |<router-link to="/about">About</router-link></nav><router-view></router-view></div>
</template><script>
export default {name: "App",
};
</script>

4.3.?路由解析

通過router.js匹配對應組件文件,以下是router.js文件代碼:

// src/route.js
import {createRouter as _createRouter,createMemoryHistory,createWebHistory,
} from "vue-router";
import Home from "./pages/Home.vue";
import About from "./pages/About.vue";const routes = [{ path: "/", component: Home },{ path: "/about", component: About },
];export function createRouter() {return _createRouter({history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),routes,});
}

4.4.?數據預取

數據預取通常是通過執行組件asyncData方法獲取數據注入到組件文件里,本例中為了方便演示已省略。

以下是About.vue文件代碼:

<!--src/pages/About.vue-->
<template><div><h1>About</h1><p>This is the about page.</p></div>
</template><script>
export default {name: "About",
};
</script>

以下是Home.vue文件代碼:

<!--src/pages/Home.vue-->
<template><div><h1>Home</h1><p>Count: {{ count }}</p><button @click="increment">Increment</button></div>
</template><script setup>
import { useCounterStore } from "../store";
import { storeToRefs } from "pinia";const store = useCounterStore();
const { count } = storeToRefs(store);
const { increment } = store;</script>

4.5.?狀態同步

準備初始狀態,以下是store的代碼:

// src/store/counter.jsimport { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {state: () => ({count: 10,}),actions: {increment() {this.count++;},},
});

4.6.?HTML生成

Vue SSR 將組件樹遞歸渲染為 HTML 字符串,包含初始狀態和激活標記,用于服務端返回完整頁面結構。

4.7.?響應返回

將響應的結果注入狀態到模板中,以下是index.html文件代碼:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Vue 3 SSR Example</title>
</head><body><div id="app"><!--app-html--></div><script>window.__INITIAL_STATE__ = "<!--pinia-state-->";</script><script type="module" src="/src/entry-client.js"></script>
</body></html>

4.8.?客戶端激活

客戶端激活頁面交互,以下是entry-client.js文件代碼:

// /src/entry-client.js// 導入應用創建函數和狀態管理庫
import { createApp } from "./main";
import { createPinia } from "pinia";// 創建Vue應用實例(包含應用、路由和狀態管理)
const { app, router, pinia } = createApp();// 服務端渲染注入的初始狀態處理
// 從全局變量獲取服務端序列化的狀態數據
if (window.__INITIAL_STATE__) {try {// 將JSON字符串還原為Pinia狀態對象pinia.state.value = JSON.parse(window.__INITIAL_STATE__);} catch (e) {// 解析失敗時輸出錯誤信息(開發環境調試用)console.error("Failed to parse initial state:", e);}
}// 等待路由導航準備就緒后掛載應用
// 確保異步路由組件解析完成后再執行掛載
router.isReady().then(() => {// 將Vue實例掛載到ID為app的DOM節點// 客戶端hydration的入口點app.mount("#app");
});

5. 效果預覽

觀察控制臺返回的結果,可以清楚的看到文件不再只是一個空殼文件,而是帶有樣式的頁面,在瀏覽器上點擊按鈕數字均有變化,說明事件和狀態已經被客戶端激活了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/92862.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/92862.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/92862.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

機器翻譯:需要了解的數學基礎詳解

文章目錄一、概率論與統計學1.1 基本概念1.2 在機器翻譯中的應用二、線性代數2.1 基本概念2.2 在機器翻譯中的應用三、微積分3.1 基本概念3.2 在機器翻譯中的應用四、信息論4.1 基本概念4.2 在機器翻譯中的應用五、數值優化5.1 優化問題形式化5.2 優化算法5.3 正則化技術六、圖…

藍橋杯手算題和雜題簡易做法

一、巧用Excel Excel在解決某些數學問題時非常高效&#xff0c;特別是涉及表格計算、簡單統計和可視化分析時。 門牌制作 這道題是一道基礎題&#xff0c;只需要判斷每個數字有幾個2&#xff0c;然后在加起來即可&#xff0c;但是還有更簡單的方法&#xff0c;先通過編譯器&…

5. 緩存-Redis

文章目錄前言一、 介紹1. 簡介2. 核心特點二、 應用場景1. 應用場景2. 數據類型作用場景三、 性能特性1. 內存2. 高性能數據結構3. 單線程、多路復用四、 異步持久化機制1. RDB&#xff08;Redis Database&#xff09;2. AOF&#xff08;Append-Only File&#xff09;3. 持久化…

如何理解Tomcat、Servlet、Catanalina的關系

目錄 背景&#xff1a; 結論&#xff1a; 好文-【拓展閱讀】&#xff1a; 象漂亮更新動力&#xff01; 背景&#xff1a; 學習Java的Servlet時&#xff0c;常常說Tomcat是一個容器&#xff0c;我們寫ServletA,ServletB,Tomcat容器在啟動的時候會讀取web.xml或者我們程序中的…

Hive的并行度的優化

對于分布式任務來說&#xff0c;任務執行的并行度十分重要。Hive的底層是MapReduce&#xff0c;所以Hive的并行度優化分為Map端優化和Reduce端優化。(1)、Map端優化Map端的并行度與Map切片數量相關&#xff0c;并行度等于切片數量。一般情況下不用去設置Map端的并行度。以下特殊…

Vue.js 響應接口:深度解析與實踐指南

Vue.js 響應接口&#xff1a;深度解析與實踐指南 引言 隨著前端技術的不斷發展&#xff0c;Vue.js 作為一種流行的前端框架&#xff0c;已經成為了眾多開發者的首選。Vue.js 的響應式系統是其核心特性之一&#xff0c;它允許開發者輕松實現數據的雙向綁定。而響應接口則是Vue.j…

高精度藍牙定位:技術、應用與未來發展

一、高精度藍牙定位概述在當今科技飛速發展的時代&#xff0c;定位技術的精度和可靠性變得越來越重要。高精度藍牙定位作為一種新興的定位技術&#xff0c;正逐漸嶄露頭角。藍牙技術是一種支持設備短距離通信&#xff08;一般10m內&#xff09;的無線電技術&#xff0c;能在包括…

C# 基于halcon的視覺工作流-章29-邊緣提取-亞像素

C# 基于halcon的視覺工作流-章29-邊緣提取-亞像素 本章目標&#xff1a; 一、1edges_sub_pix&#xff1b; 二、threshold_sub_pix&#xff1b;本實例實現過程與章28基本相同&#xff0c;不同處在于提取的邊緣是亞像素&#xff0c;精度較高&#xff0c;本文僅介紹不同之處&#…

如何實現PostgreSQL的高可用性,包括主流的復制方案、負載均衡方法以及故障轉移流程?

前言 實現 PostgreSQL 的高可用性&#xff08;High Availability, HA&#xff09;是一個系統工程&#xff0c;需要結合復制技術、連接路由&#xff08;負載均衡&#xff09;、自動故障轉移&#xff08;Failover&#xff09;以及監控告警。以下是主流方案和關鍵流程的詳細說明&a…

Apache Ignite 生產級的線程池關閉工具方法揭秘

Apache Ignite 中用于 安全、可靠地關閉線程池&#xff08;ExecutorService&#xff09; 的關鍵邏輯。我們來一步步深入理解它的設計思想和實現細節。&#x1f9f1; 一、核心方法&#xff1a;U.shutdownNow(...) public static void shutdownNow(Class<?> owner, Nullab…

Unity:GUI筆記(一)——文本、按鈕、多選框和單選框、輸入框和拖動條、圖片繪制和框繪制

寫在前面&#xff1a;寫本系列(自用)的目的是回顧已經學過的知識、記錄新學習的知識或是記錄心得理解&#xff0c;方便自己以后快速復習&#xff0c;減少遺忘。主要是唐老師的課程。一、重要參數、文本、按鈕GUI相關代碼需要寫在private void OnGUI()中。該函數每幀執行&#x…

wordpress從wp_nav_menu中獲取菜單項

從wp_nav_menu中獲取菜單項&#xff0c;然后檢查這些菜單項是否對應分類(Category)&#xff0c;并輸出這些分類的ID。 以下是完整的代碼實現&#xff1a; <?php // 獲取指定菜單位置的菜單項 $menu_items wp_get_nav_menu_items(wodepress); // wodepress 是菜單位置的名…

第4章 程序段的反復執行2 while語句P128練習題(題及答案)

&#xff08;&#xff08;1&#xff09;閱讀程序#include <bits/stdc.h> using namespace std; //湯永紅 int main(){int n,s0;cin >> n;while(n){s s * 10 n % 10;n / 10;}cout << s << endl;return 0; }分別輸入&#xff1a;0 1024 1234567890輸出…

圖解軟件系統組成

這是基于 ??PlantUML?? 繪制的軟件系統組成部分思維導圖&#xff0c;聚焦技術路線與文件類型的對應關系&#xff0c;采用分層架構展示核心模塊&#xff1a;startmindmap * **軟件系統組成部分*** **一、核心技術棧*** 后端技術* 技術路線: Python Web 框架* 文件類型: .py …

【傳奇開心果系列】Flet框架實現的多人訪問web數據表高并發前后端自定義框架模板

Flet框架實現的多人訪問web數據表高并發前后端自定義框架模板一、效果展示截圖二、應用場景介紹1. **多用戶實時協作**2. **產品管理**3. **數據可視化**三、特色說明1. **實時通信**2. **高性能**3. **用戶友好的界面**4. **日志記錄**5. **安全性**四、總結五、源碼下載地址六…

農業智慧大屏系統 - Flask + Vue實現

下面我將實現一個完整的農業智慧大屏系統&#xff0c;使用Flask作為后端框架&#xff0c;前端使用Vue.js結合ECharts進行數據可視化展示。 設計思路 前端部分&#xff1a; 使用Vue.js構建響應式界面 使用ECharts實現各類農業數據可視化 使用CSS Grid布局實現大屏適配 后端…

Linux中Https配置與私有CA部署指南

Linux中Https配置與私有CA部署指南 一、HTTPS 核心概念特性HTTPHTTPS協議明文傳輸HTTP SSL/TLS端口80443加密未加密數據加密二、SSL/TLS 握手流程 Client → Server ClientHello&#xff1a;支持哪些版本、支持哪些加密算法&#xff0c;隨機生成一組32字節數據 random_c Serve…

【軟考架構】主流數據持久化技術框架

JDO與JPA JDO&#xff08;Java Data Objects&#xff09;和JPA&#xff08;Java Persistence API&#xff09;都是Java中用于對象持久化的規范&#xff0c;但它們在設計目標、技術背景和應用場景上存在顯著區別。以下是兩者的核心對比&#xff1a;1. 規范背景與維護方 JDO&…

服務日志、監控

服務怎么做監控和告警使用 Prometheus 和 Grafana 來實現整個微服務集群的監控和告警&#xff1a;Prometheus&#xff1a;Prometheus 是一個開源的監控系統&#xff0c;具有靈活的數據模型和強大的查詢語言&#xff0c;能夠收集和存儲時間序列數據。它可以通過 HTTP 協議定期拉…

秋招筆記-8.12

我決定從今天開始&#xff0c;在每天的學習內容中加入算法的內容&#xff0c;大致分布時間的話&#xff0c;假設我一天可以學習八個小時&#xff0c;那算法兩個小時&#xff0c;八股三個小時&#xff0c;項目三個小時這樣的分布差不多吧。之所以還是需要做做筆試一是為了應對面…