山南市网站建设_网站建设公司_定制开发_seo优化
2026/1/8 0:13:35 网站建设 项目流程

本文介绍如何使用 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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询