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;项目三个小时这样的分布差不多吧。之所以还是需要做做笔试一是为了应对面…