怎么使用 Vue 框架实现 SSR

330 阅读4分钟

一、SSR 核心概念

  1. 同构应用

    • 服务端渲染(Node.js)和客户端渲染(浏览器)共用同一套代码
    • 服务端生成完整 HTML,客户端激活为交互应用
  2. 关键优势

    • 更好的 SEO(搜索引擎优化)
    • 首屏加载速度提升
    • 统一的代码逻辑

二、实现方式选择

  1. 官方推荐方案

    • Nuxt.js:基于 Vue 的高层框架,内置 SSR 支持(约定优于配置)
    • Vue CLI + @vue/server-renderer:手动配置(适合定制化需求)
  2. 本示例采用手动配置方式

    • 理解 SSR 底层原理
    • 便于根据项目需求灵活调整

三、基本实现步骤

1. 项目初始化

bash

npm init -y
npm install vue @vue/server-renderer express

2. 目录结构设计

plaintext

project/
├── src/
│   ├── app.js        # Vue应用实例
│   ├── entry-client.js # 客户端入口
│   ├── entry-server.js # 服务端入口
│   └── components/    # 组件目录
├── server.js         # 服务器主文件
├── webpack.client.config.js # 客户端打包配置
└── webpack.server.config.js # 服务端打包配置

3. 创建 Vue 应用

javascript

// src/app.js
import { createApp } from 'vue'
import App from './App.vue'

export function createVueApp() {
  const app = createApp(App)
  return { app }
}

4. 服务端入口

javascript

// src/entry-server.js
import { createVueApp } from './app'

export default (context) => {
  return new Promise((resolve, reject) => {
    const { app } = createVueApp()
    
    // 这里可以进行路由匹配和数据预取等操作
    
    resolve(app)
  })
}

5. 客户端入口

javascript

// src/entry-client.js
import { createVueApp } from './app'

const { app } = createVueApp()

// 挂载应用到DOM
app.mount('#app')

6. 服务器配置

javascript

// server.js
const express = require('express')
const { createSSRApp } = require('vue')
const { renderToString } = require('@vue/server-renderer')
const server = express()
const fs = require('fs')
const path = require('path')

// 静态资源处理
server.use(express.static(path.resolve(__dirname, './dist/client')))

// 服务端渲染处理
server.get('*', async (req, res) => {
  const template = fs.readFileSync(
    path.resolve(__dirname, './dist/client/index.html'),
    'utf-8'
  )
  
  // 创建Vue应用实例
  const { createVueApp } = require('./dist/server/entry-server.js')
  const { app } = createVueApp()
  
  // 将Vue应用渲染为HTML字符串
  const appHtml = await renderToString(app)
  
  // 替换模板中的占位符
  const html = template.replace('<!--app-html-->', appHtml)
  
  // 返回渲染后的HTML
  res.send(html)
})

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000')
})

7. Webpack 配置

javascript

// webpack.client.config.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  target: 'web',
  entry: './src/entry-client.js',
  output: {
    path: path.resolve(__dirname, './dist/client'),
    filename: 'client-bundle.js'
  },
  module: {
    rules: [
      { test: /.vue$/, use: 'vue-loader' },
      { test: /.js$/, use: 'babel-loader' }
    ]
  },
  plugins: [new VueLoaderPlugin()]
}

javascript

// webpack.server.config.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  target: 'node',
  entry: './src/entry-server.js',
  output: {
    path: path.resolve(__dirname, './dist/server'),
    filename: 'server-bundle.js',
    libraryTarget: 'commonjs2'
  },
  module: {
    rules: [
      { test: /.vue$/, use: 'vue-loader' },
      { test: /.js$/, use: 'babel-loader' }
    ]
  },
  plugins: [new VueLoaderPlugin()]
}

四、构建和运行

bash

# 安装依赖
npm install webpack webpack-cli vue-loader vue-template-compiler babel-loader @babel/core @babel/preset-env --save-dev

# 构建客户端和服务端包
npx webpack --config webpack.client.config.js
npx webpack --config webpack.server.config.js

# 启动服务器
node server.js

一、SSR 的主优缺点如下:

一、SSR 的主要优点

1. SEO 友好

  • 原理:搜索引擎爬虫直接获取完整 HTML 内容,无需执行 JavaScript。
  • 场景:电商产品页、新闻资讯、博客等需要搜索引擎收录的页面。
  • 对比 CSR:传统 SPA(单页应用)的内容由 JS 动态生成,爬虫可能无法抓取有效信息。

2. 首屏加载速度更快

  • 原理:用户首次请求时直接获得渲染好的 HTML,无需等待 JS 加载和执行。
  • 数据:Google 研究表明,页面加载时间每增加 1 秒,转化率下降 20%。
  • 关键指标:TTFB(Time To First Byte)显著缩短,尤其在弱网环境下优势明显。

3. 更好的用户体验

  • 无白屏等待:直接展示完整内容,减少用户感知的加载时间。
  • 适用场景:内容型网站(如知乎、微博)、电商详情页等。

4. 统一的代码逻辑

  • 同构应用:服务端和客户端共用大部分代码(如路由、状态管理)。
  • 维护成本:减少代码冗余,提升开发效率。

5. 性能优化潜力

  • 组件级缓存:服务端缓存渲染结果,重复请求直接返回 HTML 片段。
  • 按需加载:结合客户端 hydration,只加载当前页面所需资源。

二、SSR 的主要缺点

1. 开发复杂度高

  • 环境差异:服务端没有浏览器 API(如windowlocalStorage),需要条件判断。
  • 生命周期差异:部分 Vue/React 生命周期钩子仅在客户端执行。
  • 调试困难:服务端和客户端的错误堆栈可能不一致。

2. 服务器负载增加

  • 性能开销:每个请求都需要动态渲染 HTML,CPU 密集型操作。
  • 成本影响:需要更多服务器资源,尤其在高并发场景下。

3. 部署和维护复杂

  • 环境依赖:需要 Node.js 运行环境,传统静态站点部署方案不适用。
  • 监控困难:服务端渲染错误可能导致整个页面无法访问。

4. 首屏交互延迟

  • hydration 过程:客户端接管 HTML 后需要重新绑定事件,在此期间交互可能无响应。
  • 解决方案:使用vueuse/coreuseIntersectionObserver等工具优化。