news 2026/4/15 9:40:30

「从零到一」我用 Node BFF 手撸一个 Vue3 SSR 项目(附源码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
「从零到一」我用 Node BFF 手撸一个 Vue3 SSR 项目(附源码)

本文介绍如何使用 Node.js 作为中间层(BFF),结合 Vue 3 和 Vite 实现服务端渲染(SSR)。

为什么需要 SSR?

在传统的单页应用(SPA)中,浏览器首先加载一个空白的 HTML,然后通过 JavaScript 动态渲染页面内容。这种方式存在两个明显的问题:

问题影响
首屏加载慢用户需要等待 JS 下载、解析、执行后才能看到内容
SEO 不友好搜索引擎爬虫可能无法正确索引动态生成的内容

SSR(Server-Side Rendering)可以很好地解决这些问题——在服务端就把页面渲染成完整的 HTML,浏览器拿到后直接展示。

什么是 BFF?

BFF(Backend For Frontend)是一种架构模式,指的是专门为前端服务的后端层。在 SSR 场景中,Node.js 作为 BFF 承担两个核心职责:

┌─────────────────────────────────────────────────────────┐ │ Node BFF 职责 │ ├─────────────────────────────────────────────────────────┤ │ 1️⃣ SSR 渲染:将 Vue 组件渲染为 HTML 字符串 │ │ 2️⃣ API 代理:提供前端需要的接口,聚合后端服务 │ └─────────────────────────────────────────────────────────┘

项目架构

我实现的这个 SSR 项目整体架构如下:

浏览器请求 │ ▼ ┌───────────────────────────────────────┐ │ Node.js (Express) │ │ BFF 中间层 │ ├───────────────────────────────────────┤ │ • 处理 /api/* 请求 → 返回 JSON │ │ • 处理页面请求 → SSR 渲染 HTML │ └───────────────────────────────────────┘ │ ▼ 浏览器显示 → JS 加载 → Hydration 激活

核心实现

1. 项目入口分离

SSR 项目需要两个入口文件:

文件运行环境职责
entry-server.jsNode.js渲染 Vue 组件为 HTML 字符串
entry-client.js浏览器激活静态 HTML,恢复交互能力

服务端入口entry-server.js

import { renderToString } from "vue/server-renderer"; import { createApp } from "./main.js"; export async function render(url) { const { app, router, pinia } = createApp(); // 设置服务端路由 router.push(url); await router.isReady(); // 渲染为 HTML 字符串 const html = await renderToString(app); // 返回 HTML 和状态(用于客户端还原) const state = pinia.state.value; return { html, state }; }

客户端入口entry-client.js

import { createApp } from "./main.js"; async function hydrate() { const { app, router, pinia } = createApp(); await router.isReady(); // 还原服务端状态 if (window.__PINIA_STATE__) { pinia.state.value = window.__PINIA_STATE__; } // 激活服务端渲染的 HTML app.mount("#app"); } hydrate();

2. BFF 服务器实现

这是整个 SSR 的核心——server.js

import express from "express"; import { renderToString } from "vue/server-renderer"; async function createServer() { const app = express(); // ========== BFF API 接口 ========== app.get("/api/hello", (req, res) => { res.json({ message: "你好,这是来自 Node BFF 的响应!", timestamp: new Date().toISOString(), }); }); // ========== SSR 渲染 ========== app.use("*", async (req, res) => { const url = req.originalUrl; // 1. 读取 HTML 模板 let template = fs.readFileSync("index.html", "utf-8"); // 2. 加载服务端入口 const { render } = await import("./src/entry-server.js"); // 3. 执行渲染 const { html: appHtml, state } = await render(url); // 4. 注入渲染结果 let finalHtml = template.replace("<!--ssr-outlet-->", appHtml); // 5. 注入初始状态 finalHtml = finalHtml.replace( "</head>", `<script>window.__PINIA_STATE__ = ${JSON.stringify( state )}</script></head>` ); // 6. 返回完整 HTML res.status(200).set({ "Content-Type": "text/html" }).end(finalHtml); }); app.listen(3000); }

3. 状态管理与 Hydration

SSR 最关键的一步是状态同步

服务端渲染时 客户端激活时 │ │ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ Pinia 状态 │ ──序列化注入HTML──→ │ 还原状态 │ └─────────────┘ └─────────────┘ │ │ ▼ ▼ 渲染 HTML Hydration

服务端会将 Pinia 状态序列化后注入到 HTML 中:

<script> window.__PINIA_STATE__ = { counter: { count: 5 } }; </script>

客户端加载后读取这个状态,确保 Hydration 时状态一致,避免"闪烁"问题。

SSR 完整流程

整个 SSR 的工作流程可以总结为:

┌──────────────────────────────────────────────────────────────┐ │ SSR 完整流程 │ └──────────────────────────────────────────────────────────────┘ │ ┌─────────────────────┴─────────────────────┐ ▼ ▼ 【服务端阶段】 【客户端阶段】 │ │ 1. 接收请求 4. 浏览器显示 HTML │ │ 2. Vue 组件渲染为 HTML 5. 加载客户端 JS │ │ 3. 注入状态并返回 6. Hydration 激活 │ 7. 应用可交互 ✨

开发与生产环境

项目支持两种运行模式:

模式特点
开发模式集成 Vite,支持 HMR 热更新
生产模式预构建产物,性能更优
# 开发模式 npm run dev # 生产构建 + 启动 npm run build npm run serve

功能演示

演示内容:

  • • ✅ 计数器状态管理(Pinia)
  • • ✅ BFF API 调用(Express)
  • • ✅ 路由切换(Vue Router)
  • • ✅ 客户端 Hydration

总结

通过这个项目,我实现了一个完整的 Vue 3 SSR 应用,核心要点:

    1. BFF 架构:Node.js 同时承担 API 服务和 SSR 渲染职责
    1. 入口分离:服务端和客户端各自有独立的入口文件
    1. 状态同步:服务端状态序列化注入 HTML,客户端还原后 Hydration
    1. Vite 加持:开发体验极佳,HMR 秒级刷新

如果你也想了解 SSR,欢迎 Clone 这个项目学习:

git clone https://github.com/xiaogao007/vue3-ssr-demo

作者:小高
项目地址:vue3-ssr-demo

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/12 8:00:38

CMAKE指令集

目录 1、指定CMAKE最低的版本号 2、设置工程名 3、添加可执行文件 4、简化项目名的表示 5、添加多个可执行文件 6、添加多个可执行文件的简洁方法 7、添加版本号和配置头文件 8、指定C版本 9、添加库 10、使用库 11、将库设置为可选项 如果这篇文章能帮助到你&…

作者头像 李华
网站建设 2026/4/14 3:54:39

AI写作助手测评大会

当前主流AI写作工具对比ChatGPT (OpenAI) 支持长文本生成和多轮对话&#xff0c;擅长创意写作和内容改写。提供网页版和API接口&#xff0c;订阅版可访问GPT-4模型。在学术写作和商业文案场景表现突出。Claude (Anthropic) 注重安全性和事实准确性&#xff0c;内置宪法AI约束机…

作者头像 李华
网站建设 2026/4/8 21:11:22

【必收藏】突破传统RAG瓶颈:Deep Thinking RAG架构详解与实战指南

Deep Thinking RAG是一种将RAG与Agent技术融合的新型架构&#xff0c;突破了传统RAG的局限性。它通过四个智能模块&#xff08;规划代理、检索监督者、多阶段检索漏斗、策略代理&#xff09;实现从线性链到循环图的跃迁&#xff0c;支持多跳推理、动态知识边界和自适应检索策略…

作者头像 李华
网站建设 2026/4/14 13:52:56

查询指定任务的办理时间轴

一、接口核心需求分析 你提供的这段代码是Activiti工作流中查询指定任务的办理时间轴接口&#xff0c;核心业务需求如下&#xff1a; 关联查询&#xff1a;根据传入的任务ID&#xff08;taskId&#xff09;&#xff0c;先获取对应的流程实例ID&#xff08;processInstanceId&am…

作者头像 李华
网站建设 2026/4/13 9:56:29

ubuntu系统_每天定时23:00 定时关机_怎么实现

在Ubuntu系统中设置每天23:00定时关机&#xff0c;最可靠的方法是使用系统内置的 cron 服务。下面的表格汇总了实现步骤和关键要点&#xff0c;你可以快速了解整个过程。步骤核心操作说明/命令示例1. 编辑计划任务终端中输入 sudo crontab -e使用 sudo 获取root权限&#xff0c…

作者头像 李华
网站建设 2026/4/14 10:11:13

mysql一条sql语句是如何运行的

MySQL SQL 语句执行流程MySQL 执行一条 SQL 语句的过程可以分为多个阶段&#xff0c;包括连接管理、解析与优化、执行引擎处理以及结果返回。以下是详细流程&#xff1a;连接管理客户端通过 TCP/IP 或 Unix Socket 连接到 MySQL 服务器。连接建立后&#xff0c;服务器进行身份验…

作者头像 李华