✨
blog
  • Blog
  • Element-UI
    • 2019-09-04
  • JS
    • ES6 之 Set 和 Map
    • let 和 const 声明常见概念
    • 元编程
    • ES6之字符串的扩展
    • ES6 之异步流程的前世今生(上)
    • ES6之异步流程的前世今生(下)
    • ES6 之模块你知吗
    • ES6 之解构赋值与箭头函数的妙用
    • 迭代器
    • ES5 之原型(一)
    • ES6之类(二)
    • es7之装饰器
    • es6之数组详解
    • js之this指向
    • 对象
    • vue项目配合使用canvas联动
    • 本文解决痛点:对象里面是否有值
  • MAC
    • vue源码之method
    • Mac的使用技巧
    • 前文
    • Mac常用软件(二)
    • 如何查看 Mac 端口号以及占用情况
  • Node
    • Node之Buffer详解
    • 浏览器与 node 的事件循环(event loop)有何区别
    • Node之多线程
    • node之模块解析(一)
    • 错误捕获与内存告警
  • TS
    • Record
    • 使用方法
    • 工具泛型
    • 类型体操
    • 泛型
  • chrome
    • v8 引擎
    • v8 垃圾回收机制
    • 浏览器的知识
  • flutter
    • 路由
    • 页面布局
  • go
    • index
  • html&css
    • 两栏布局
    • ES5和ES6的区别
    • ES5 和 ES6 的区别
    • HTTP详解
    • TCP 与 UDP 的区别
    • MDN
    • css modules 使用教程
    • css 居中
    • 拖拽
    • flex布局
    • h5 新增特性 html5
    • history 与 hash 路由策略
    • position 定位方式
    • rem布局
    • svg
    • web性能优化
    • 事件循环
    • 从输入网址后发生了什么
    • 前端状态管理
    • 圣杯布局与双飞翼
    • 性能优化 页面的性能统计指标
    • 本地存储的几种对比
    • 浏览器的渲染进程
    • 浏览器缓存策略详解
    • 盒模型
    • 为什么要移动端适配
    • 跨域的 N 种实现方式
  • web3
    • 常见概念
    • vue项目配合使用canvas联动
  • webgl
    • Mac使用技巧(二)
    • Node之模块解析path
  • 代码库
    • documeng的一些常见操作
    • eventBus事件
    • jquery提交
    • jquery的一些常见操作
    • 常见操作
    • 数组polyfill
    • TS代码片段
      • 面试官眼中的test unit
  • 全年安排
    • AfterShip
    • 大企业
  • 函数编程题
    • Promise问题
    • 继承
  • 前端早早聊
    • vue生态
    • 开发一款VScode语言插件
    • 简历回顾和进行复盘
    • 重新认知性能优化及其度量方法
    • 2022-09-17-音视频专场.md
      • 2022-09-17-音视频专场
    • 前端晋升专场
      • 成长的诀窍是靠自己
      • 销销帮
    • 前端监控专场
      • 字节前端监控实践
      • 李港:大前端-从无到有搭建自研前端监控系统
    • 前端跳槽
      • 50个面试官在线招聘
      • 如何识别优秀的前端猎头来跳槽大厂
      • 面试套路
    • 支付宝
      • 面试
    • 管理专场
      • 芋头:管理者眼中的web技术发展前沿
    • 组件专场
      • 基于webCompents的跨技术组件库实践
    • 面试
      • 面试辅导问题
      • 早早聊面试
      • 前端沙箱是什么? 怎么实现沙箱环境?
  • 常见总结
    • 2018年终总结-年底了,你总结了吗?我先来
    • 在逆境中成长
    • 2021年终总结
    • 2024年全年总结
    • 项目
    • Tell2.0 前端复盘
    • 复盘
    • 前端工程师素养
    • 学习方法论
    • 希望与破晓| 2022 年终总结
    • 新起点, 新征途 | 掘金年度征文
    • 稳定| 2023 年终总结
    • 趁着有风快飞翔 | 2019 年终总结
    • AfterShip
      • Emotion:用 JavaScript 编写 CSS 的强大工具
      • 个人中长期目标
      • 事故复盘
      • 时间解析
      • 国内外区别
      • 独立站建设
    • MEIZU
      • NativeApp与H5通信原理
      • SSR 原理
      • SSR的常见问题
      • CLI
      • electron 应用发布流程
      • electron
      • electron 面试
      • 数据结构与算法之美
      • mgc 一期复盘
      • 架构原理
      • 喵币管理
      • 三期复盘总结
      • 异常监控之 sentry 实践
      • 微前端
      • qiankun 原理解析
      • 快游戏一期
      • 游戏中心复盘
    • 个人准则
      • index
    • 编程猫
      • pc 接入 micro bit 方案
      • prompt engineer
      • web work 跨域解析与解决方式
      • web 中的 ai
      • 低版本 node 环境下 ffmpeg 的使用
      • 关于 taobao 源 https 过期
      • 加密 json
      • 安卓 5 和 6 的白屏解决
      • 性能排查与优化实践
      • 探月接入
      • 接入硬件
      • 新生态下的state
      • monorepo 包管理方式
      • 自修复 npm 库
      • 音频的绘制
    • 谨启
      • 音视频
      • 小程序
        • taro 规范
        • 结合 mobx 在跳转前预请求
        • Taro 浅析用法与原理
        • 前文
        • 小程序优化指南
        • 小程序内部实现原理
        • 支付相关
    • tencent
      • TAPD
        • MathJax的食用
        • canvas渲染优化策略
        • 为什么 JavaScript 是单线程的呢?
        • svg 总是不对
        • 前端库
        • 原生端和js端如何通信
        • 在旧项目中复用vue代码
        • 提升自我
        • 批量编辑优化
        • 插入业务对象
        • 编辑器
        • 挂载点
        • 性能优化对比
        • 遇到的问题
        • 项目迁移公告
        • 领导力
      • 行家
        • 实战篇
        • 职业发展、领导力、个人成长
        • 高质量沟通
  • 慕课网
    • react-native原理
    • react-native学习
  • 杂文
    • Dom 节点变动检测并录制的简单实现
    • 错误监控&错误捕获
    • NextJS与NuxtJS
    • 负载均衡的几种常用方式
    • PM2
    • service worker 控制网络请求?
    • SSL 和 TLS 的区别
    • Babel 你太美
    • echart踩坑经验
    • keyup、keydown你都知道有什么区别吗
    • 常见概念
    • 首屏加载优化与性能指标分析
    • preload 和 prefetch 的详解
    • 在项目中配置这几个关系
    • roullp 解析
    • tinymce原理浅析
    • wasm 在前端的应用
    • websocket
    • webworker
    • 项目
    • 从 ajax 到 axios
    • 从postcss 到自己开发一款插件
    • 从输入浏览器到页面展示涉及的缓存机制
    • 代码整洁之道
    • 你知道什么是aop吗
    • 函数式编程
    • 函数式编程指南
    • 前端input框文字最大值
    • 攻坚战
    • 前端书写 sdk
    • 前端文字转语音播放
    • 前端领域的 Docker 和 Kubernetes
    • 前端安全
    • 前端进阶之内存空间
    • 前端音频浅析
    • 十分钟搞定多人协作开发
    • 字符串的比较
    • 尾递归
    • 前文
    • 常见的算法可以分为以下三类
    • 手机调试--mac篇
    • 数组的原生系列
    • COOP 和 COEP - 新的跨域策略
    • 浅谈react组件书写
    • 浏览器与 Node.js 事件循环的区别
    • 由三道题引伸出来的思考
    • 移动端300ms点击延迟
    • 移动端和pc端事件
    • Git 常见疑惑
    • 我们离发 npm 包还有多远
    • 重绘和重排
    • AI 时代下的前端编程范式
    • 音频可视化实战
  • 极客时间
    • Serverless入门课
    • 二分查找
    • 二叉树
    • 全栈工程师
    • 动态规划面试宝典
    • 前端与rust
    • 散列表
    • 前端方面的Docker和Kubernetes
    • 栈
    • 深入浅出区块链
    • 玩转 vue 全家桶
    • 玩转 webpack
    • 程序员的个人财富课
    • 算法
    • 说透元宇宙
    • 跳表
    • 链表
    • 10x 程序员工作法
      • index
    • Node开发实战
      • HTTP服务的性能测试
      • JavaScript语言精髓与编程实战
      • 什么是node。js
      • svg精髓
    • ReactHooks核心原理与实战
      • ReactHooks核心原理与实战
    • Rust
      • Rust编程第一课
      • 前置篇
      • 深度思维
      • 重构
      • 类型体操
      • 基础知识
    • WebAssembly入门课.md
      • 基础篇
      • SSR的注水和脱水
      • jsBriage通信原理
      • 基础知识篇
    • 互联网的英语私教课
      • 互联网人的英语私教课
    • 代码之丑
      • 代码之丑
    • 前端全链路优化实战课
      • 网页指标
    • 图解 Google V8
      • 图解 Google V8
    • 浏览器工作原理与实践
      • 浏览器工作原理与实践
    • 算法面试通关 40 讲
      • 算法面试通关40讲
    • 跟月影学可视化
      • index
    • 软件设计之美
      • 软件设计之美
    • 重学前端
      • js
  • 后续的文件增加都会增加到上面并以编号对应
    • 1029. 两地调度
    • 151.翻转字符串里的单词
    • 2022.3.15
    • 前端数据结构
    • 前端常见算法
    • 前端常见排序
    • 恢复一棵树
  • 设计模式
    • 前端常见设计模式之MVC与MVVM
    • 前端之代理模式
    • 前端常见设计模式之单例模式
    • 前端常见设计模式之发布订阅模式
    • 前端之工厂模式
    • 观察者模式
    • 前端常见设计模式之适配器模式
  • 译文
    • [译] 如何使用CircleCI for GitHub Pages持续部署
    • 您是否优化了 API 的性能
    • [译][官方] Google 正式发布 Flutter 1.2 版本
    • 什么是 Deno ,它将取代 NodeJS ?
  • 读后感
    • JavaScript二十年
    • 1368个单词就够了
    • js编程精解
    • labuladong 的算法小抄
    • lodash常用方法
    • vue的设计与实现
    • 所有的静态资源都是get请求
    • 人生
    • 人生护城河
    • 你不知道的JavaScript
    • 前端核心知识进阶
    • 华为工作法
    • 反脆弱
    • 好好学习
    • 左耳听风
    • 摩托车维修之道
    • 数学之美
    • 深入理解svg
    • 浏览器的ESM到底是啥
    • 经济学原理
    • 编程珠玑
    • 防御式 css 精讲
    • 韭菜的自我修养
  • 雪狼
    • 2022-07-17
    • 基础知识
    • 阶一课程
      • 实战辅导一
      • 实战辅导二
  • 嵌入式
    • 树莓派
      • 排序
  • 源码
    • React
      • 核心知识点
      • errorBoundaries
      • immutable.js 的实现原理
      • React.Suspense
      • react源码分析之Fiber
      • batchedUpdate
      • Component
      • Context
      • react 源码分析之 diff 算法
      • React 中的 key 属性:原理、使用场景与注意事项
      • 使用方式
      • react源码分析之memo
      • react 源码分析之mixin
      • 实战篇
      • react源码分析之react-dom
      • 使用方式
      • scheduleWork
      • useImperativeHandle的使用与原理
      • React 书写小技巧
      • 入口和优化
      • 合成事件和原生事件的区别
      • react 性能优化
      • 构建一个 hooks
      • 浅析 styled-components
      • 生命周期
      • 组合 vs 继承
      • 通信机制
      • 高阶组件
      • 慕课网
        • 应用篇
        • 课程导学
    • ReactHook
      • useCallback
      • useContext
      • useEffect 与 useLayoutEffect
      • useHook
      • useMemo
      • useReducer
      • 原理
      • useState
      • 总结
    • Redux
      • mobx 原理解析
      • redux-saga
      • redux-thunk
      • Mobx 和 Redux 对比
      • 使用方法
      • redux 原理
    • Vite
      • Vite原理
      • Vite配置
      • 热更新原理
      • vite 为什么生产环境用 Rollup
    • Webpack
      • PostCSS
      • Webpack5 核心原理与应用实践-loader
      • Webpack5 核心原理与应用实践-plugin
      • Webpack5 核心原理与应用实践
      • 区分
      • 升级详情
      • treeShaking(树摇Tree Shaking)
      • 编写一个自己的webpack插件plugin
      • 代码分离(code-splitting)
      • webpack 打包优化
      • 基础配置
      • webpack 打包优化
      • webpack 工作原理
      • webpack 按需加载原理
      • webpack 热更新 HMR(Hot Module Replacement)
      • 缓存
      • webpack 自定义 plugin
    • next
      • tailwind
      • 什么是水合
    • sveltejs
      • index
    • tinymce
      • 并发篇
    • 源码手写系列
      • create
      • call
      • bind
      • call
      • es6 单例
      • forEach vs Map
      • instanceOf
      • new
      • reduce
      • 取两个重复数组的交集
      • 函数柯理化
      • 动态规划
      • 基于Generator函数实现async
      • 新建 js 文件
      • 手写一个 slice 方法
      • 手写一个 webpack loader
      • Plugin
      • 手写一个寄生组合式继承
      • 二叉树
      • 链表相关的操作
      • 手动实现发布订阅
      • 数组去重
      • 数组扁平化
      • 数组
      • 构造大顶堆和小顶堆
      • 深浅拷贝 深拷贝
      • 两者对比
    • vue
      • vue2
        • vm.attrs与$listeners
        • vue 和 react 的 diff 算法比较
        • vue 源码分析
        • vue 优化的 diff 策略
        • extends
        • 核心原理篇
        • keep-alive
        • vue 源码分析之 mixins
        • vue 源码分析之 nextTick
        • vue之slot
        • vnode
        • vue 源码分析之 watch
        • 原理
        • vue 源码分析之transition
        • vue 源码分析之异步组件
        • 调用的是 watch
        • 安装
        • react源码分析之portals
        • event 的实现原理(事件的实现原理)
        • 什么是h
        • 分析provide 和 inject
        • vue 源码分析之 use
        • v-model
        • vue源码分析之vuex
        • 响应式原理
        • 初始化的流程
        • 组件更新
        • 编译
        • 父子组件生命周期
        • 原理
        • 多实例
        • Vue 面试
        • 源码研读一
        • 响应式原理
        • 常见问题
        • 数组的劫持
        • vue之自定义指令
        • 运行机制全局概览
      • vue3相比vue2的提升点
        • vue composition api
        • vue3的虚拟dom优化
        • vue3层面的双向数据绑定
        • 预处理优化
  • 重构
    • notification
      • 讲解
  • 面试
    • AfterShip经历
      • JS对URL进行编码和解码
      • ShippingLabelTemplate
      • 接入keycloak详解
      • reCAPTCHA接入
      • yalc与动态解决升级的依赖包
      • RBAC 简介
      • 多语言计划
      • 接入Google登录及其主动弹出快捷登录方式
      • 读书计划
        • 传染
        • 这就是OKR
    • 编程猫经历
      • 2024.1.16
      • 2025.2.20
      • 2025.2.21
      • 2025.2.26
      • 2025.3.28
      • 2025.3.3
      • 2025.3.7
      • 行动轨迹
      • 面试主观题
    • 腾讯经历
      • 2022.02.21
      • 2022.03.30
      • 2022.04.24
      • 2022.04.25
      • 2022.04.27
      • 2022.04.28
      • 2022.04.29
      • 2022.05.05
      • 不同公司的面试关注点不同
      • 2022.05.07
      • 2022.05.09
      • 2022.05.10
      • 2022.05.11
      • 2022.05.12
      • 2022.05.13
      • 2022.05.16
      • 2022.05.17
      • 2022.05.19
      • 2022.05.27
      • 面试
      • 行动轨迹
      • 面试主观题
    • 针对字节
      • 2022.05.14
      • 2022.05.17
      • HR面试准备
      • Promise的相关题目
      • React 进阶实践指南(二)
      • React 面试准备
      • vue 与 react 有什么不同 (react 和 vue 有什么区别)
      • TypeScript 全面进阶指南
      • cookie和session区别
      • express 面试准备 koa 中间件原理
      • next面试准备
      • requestCallBack
      • interface 与 type 异同点
      • 取消 promise
      • 如何设计一个前端项目
      • 进阶篇
      • 早早聊面试准备
      • 自动化部署
      • 挖掘项目的深度
      • 面试
      • 出题指数
    • 魅族经历
      • 2020.09.11
      • 一灯
      • 一灯
      • 一灯
      • 2020.09.20
      • 2020.09.21
      • 网易二面
      • 2020.09.23
      • 头条
      • 360 金融面试题
      • 富途一面
      • 算法
      • 字节
      • 2020.11.04
      • baidu 一面
      • meta 标签的作用
      • 字节
      • 2020.11.22
      • 2020.11.25
      • 微前端接入笔记
      • 面试的基本原则
由 GitBook 提供支持
在本页
  • vue ssr 使用
  • 什么是服务端渲染
  • 什么是预渲染(Prerender)
  • vue ssr 原理
  • 构建逻辑
  • 剖析流程
  • 开发流程
  • 后端登录接口实现以及 session 的使用
  • react ssr原理

这有帮助吗?

  1. 常见总结
  2. MEIZU

SSR 原理

vue ssr 使用

SPA应用采用的是客户端渲染,DOM节点要等待JS文件加载完毕后才会生成,所以就浮现了以上几个问题。

  • 内容到达时间(time-to-content)

  • seo

目前有 预渲染 和 服务端渲染 这两种方案

什么是服务端渲染

SSR模式下,后端拦截到路由,找到对应组件,准备渲染组件,所有的JS资源在本地,排除了JS资源的网络加载时间,接着只需要对当前路由的组件进行渲染,而页面的ajax请求,可能在同一台服务器上,如果是的话速度也会快很多。最后后端把渲染好的页面反回给前端。

注意:页面能很快的展示出来,但是由于当前返回的只是单纯展示的DOM、CSS,其中的JS相关的事件等在客户端其实并没有绑定,所以最终还是需要JS加载完以后,对当前的页面再进行一次渲染,称为同构。 所以SSR就是更快的先展示出页面的内容,先让用户能够看到。

两种技术有大量可重用的代码,客户端路由、服务器端路由、客户端Redux、服务器端Redux等,最大程度的复用这些代码,就是同构


服务端渲染是相对于客户端渲染而言的(Client Side Render), 它的渲染行为发生在服务器端, 渲染完成之后再将完整页面以HTML字符串的形式交给浏览器, 最后经过『注水』hydrate过程将一些事件绑定和Vue状态等注入到输出的静态的页面中, 由同步下发给浏览器的的Vue bundle接管状态, 继续处理接下来的交互逻辑. 这也是一种同构应用的实现(代码可以运行在客户端和服务端中).

什么是预渲染(Prerender)

无需使用 web 服务器实时动态编译 HTML,而是使用预渲染方式,在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。

如果项目中使用 webpack,你可以使用 prerender-spa-plugin 轻松地添加预渲染,后面将会具体实现。

Prerender 原理是在构建阶段就将 html 页面渲染完毕,不会进行二次渲染,也就是说,当初打包时页面是怎么样,那么预渲染就是什么样,如果页面上有数据实时更新,那么浏览器第一次加载的时候只会渲染当时的数据,等到 JS 下载完毕再次渲染的时候才会更新数据更新,会造成数据延迟的错觉。

vue ssr 原理

分为两个 entry, Client entry 和 Server entry 。Client entry 的功能很简单,就是挂载我们的 Vue 实例到指定的 dom 元素上;Server entry 是一个使用export导出的函数。主要负责调用组件内定义的获取数据的方法,获取到SSR渲染所需数据,并存储到上下文环境中。这个函数会在每一次的渲染中重复的调用。

我们通过 webpack 生成 server Bundle 和 Client Bundle, 前端会进行在服务器上通过 node 生成预渲染的 html 字符串,发送到我们的客户端以便完成初始化渲染; 而客户端 bundle 就自由了, 初始化渲染完全不依赖它.客户端拿到服务端返回的 html 字符串后, 会去"激活"这些静态 html, 使其变成由 vue 动态管理的 dom, 以便响应后续数据的变化.

构建逻辑

在vue-SSR中,会将代码打包分成两个部分: 服务端:bundle,客户端:bundle,Node.js会处理服务端bundle用于SSR,客户端bundle会在用户请求时和已经由SSR渲染出的页面一起返回给用户,然后在浏览器执行『注水』,接管Vue接下来的业务逻辑。

这里就会有一个问题, 服务端是如何将store状态交给客户端的呢, 因为整个构建流程是彼此独立的, 数据预取(在进入渲染页面之前获取到页面所需要的数据)之后交给了store, 而注水过程怎么接收store数据?

window.INITIAL_STATE, 这个 state 会在服务端渲染执行 context.state = store.state;的时候自动写入window中, 所以在客户端代码中就就可以直接通过 store.replaceState() 接收服务端预取的数据了.

剖析流程

node 服务器启动, 跑服务端 entry 里的函数,将构建好的 vue 实例渲染成 html 字符串,然后将拿到的数据混入(注入)我们的 html 字符串中,最后发送到我们客户端.

里面有初始化的 dom 结构, 有 css, 还有一个 script 标签. script 标签里把我们在服务端 entry 拿到的数据挂载了 window 上. 服务端的 vue 只是混入了个数据渲染了静态页面,客户端的 vue 才是去实现交互的!

SSR 独特之处 在 ssr 中, 创建 vue 实例, 创建 store 和 创建 router 都是套了一层工厂函数的, 目的就是避免数据的交叉污染. 在服务端只能执行生命周期中的 created 和 beforeCreated,原因是在服务端是无法操作 dom 的, 所以其他周期无法执行.

ssr 服务端请求不带 cookie, 需要手动拿到浏览器的 cookie 传给服务端的请求.

ssr 要求dom 结构规范, 因为浏览器会自动给 html 添加一些结构比如 tbody,但是客户端进行混淆服务端放回的 html 时,不会添加这些标签,导致混淆后的 html 和浏览器渲染的 html 不匹配


SSR的核心思路就是使用vue-server-renderer创建一个渲染器(renderer), 然后给这个渲染器传入Vue实例, 渲染器会得到HTML页面, 最后由Egg.js将HTML返回, 实现代码有些繁杂这

// 第 1 步:创建一个 Vue 实例 const app = new Vue({ template: <div>Hello World</div> })

// 第 2 步:创建一个 renderer const renderer = require('vue-server-renderer').createRenderer()

// 第 3 步:将 Vue 实例渲染为 HTML const html = await renderer.renderToString(app)

// 第 4 步, 返回 HTML this.ctx.body = await this.ctx.renderString(html)

开发流程

//webpack.config.server.js
  target: 'node',
  entry: path.join(__dirname, '../client/server-entry.js'),
  devtool: 'source-map',
  output: {
    libraryTarget: 'commonjs2',
    filename: 'server-entry.js',
    path: path.join(__dirname, '../server-build')
  },
  new VueServerPlugin()

VueServerPlugin 插件能够帮我们不生成 script 脚本,而是生成 json 文件

第一、server.js的作用: 是node服务器启动的文件

isDev 判断开发和生产

处理服务端渲染

我们创建两个文件 dev-ssr 和 ssr,分别对应开发时的服务端渲染情况、上线的服务端渲染情况

第二、dev-ssr.js作用:

  1. 拿到 webpack.config.server.js 定义为 serverConfig

在 node 环境去编译webpack,传入 serverConfig

const serverCompiler = webpack(serverConfig)

我们指定了我们的输出目录是在我们的内存里面

我们定义一个 bundle 用来记录 webpack 每次打包生成的文件

serverCompiler.watch({}, (err, stats) => {
  if (err) throw err
  stats = stats.toJson()
  stats.errors.forEach(err => console.log(err))
  stats.warnings.forEach(warn => console.warn(err))

  const bundlePath = path.join(
    serverConfig.output.path,
    'vue-ssr-server-bundle.json'
  )
  bundle = JSON.parse(mfs.readFileSync(bundlePath, 'utf-8'))
  console.log('new bundle generated')
})

我们用 vueServerPlugin 生成的文件名默认为 vue-ssr-server-bundle.json

我们根据我们写的 VueServerRenderer 创建一个 servrbundle,生成一个可以直接调用 renderer 的一个 function

我们还要去获取 webpack.config.server.js 帮我们打包出来的 JavaScript 文件的地址, 我们要拿到地址之后才能在我们拼接 html 的时候。把客户端的 js 路径写在里面, 这样我们把 html 返回给浏览器才能够渲染出来可以引用到客户端的 js, 才能够在客户端正式的跑起来,不然只能是空 html inject 设置为 false , 因为 vue 会自动诸如一些要符合 vue 的东西.

  const renderer = VueServerRenderer
    .createBundleRenderer(bundle, {
      inject: false,
      clientManifest
    })

clientmanifest包含的是js和css的文件名、路径等信息,不是内容信息。

我们通过 axios 拿到相关的 manifest.json, 我们需要在 webpack.client.json 里面引入 VueClientPlugin

 const clientManifestResp = await axios.get(
    'http://127.0.0.1:8000/public/vue-ssr-client-manifest.json'
  )

服务端渲染的操作 我们创建 server-render.js

module.exports = async (ctx, renderer, template) => {
  ctx.headers['Content-Type'] = 'text/html'

  const context = { url: ctx.path, user: ctx.session.user }

  try {
    const appString = await renderer.renderToString(context)
  }
}

每次服务端渲染都要生成新的 app, 我们不能用上一次渲染过的对象,再去进行下次渲染, 因为这个app 已经包含了上次状态, 会影响我们下次渲染内容, 不这么做的话,会出现内存溢出的风险。

export default () => {
  const router = createRouter()
  const store = createStore()

  const app = new Vue({
    router,
    store,
    render: h => h(App)
  })

  return { app, router, store }
}

server.render.js 返回的context 会在 client/server.entry.js 里面接收

  const appString = await renderer.renderToString(context)

我们要主动的 push 我们的路由, router.onready()只有在服务端渲染的时候才用到,比方说我们有一些异步加载路由的操作。

  router.push(context.url)

接着我们配置启动 server.js。 在 dev-ssr.js 导入 server-render.js 在 handleSSR 中进行处理。我们在sever。js 导入dev-ssr

const router = new Router()
router.get('*', handleSSR)

module.exports = router

我们采用nodemon 自动重启服务

concurrently 启动两个服务

   "dev": "concurrently \"npm run dev:client\" \"npm run dev:server\"",

vue-meta 设置我们服务端的元信息

后端登录接口实现以及 session 的使用

前端 Node 引入 Koa-session 生成客户状态机制

当浏览器访问服务器并发送第一次请求时,服务器端会创建一个session对象,生成一个类似于key,value的键值对, 然后将key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带key(cookie),找到对应的session(value)。 客户的信息都保存在session中

const KoaSeesion = require('koa-session')

react ssr原理

实现思想 核心实现分为以下几步:

  1. 后端拦截路由,根据路径找到需要渲染的react页面组件X

  2. 调用组件X初始化时需要请求的接口,同步获取到数据后,使用react的renderToString方法对组件进行渲染,使其渲染出节点字符串。

  3. 后端获取基础HTML文件,把渲染出的节点字符串插入到body之中,同时也可以操作其中的title,script等节点。返回完整的HTML给客户端。

  4. 客户端获取后端返回的HTML,展示并加载其中的JS,最后完成react同构。

1。 注册路由 配置路由路径和组件的映射,使其能够被客户端路由和服务端路由同时调用

import Index from "../pages/index";
import List from "../pages/list";

const routers = [
  { exact: true, path: "/", component: Index },
  { exact: true, path: "/list", component: List }
];

//注册页面和引入组件,存在对象中,server路由匹配后渲染
export const clientPages = (() => {
  const pages = {};
  routers.forEach(route => {
    pages[route.path] = route.component;
  });
  return pages;
})();
export default routers;

服务端

import { clientPages } from "./../../client/router/pages";

router.get("*", (ctx, next) => {
    let component = clientPages[ctx.path];
    if (component) {
        const data = await component.getInitialProps();
        //因为component是变量,所以需要create
        const dom = renderToString(
            React.createElement(component, {
                ssrData: data
            })
        )
    }
})

匹配到组件以后,执行了组件的getInitialProps方法,此方法是一个封装的静态方法,主要用于获取初始化所需要的ajax数据,在服务端会同步获取,而后通过ssrData参数传入组件props并执行组件渲染。 此方法在客户端依然是异步请求。

  1. 客户端渲染

import React from "react";
export default class Base extends React.Component {
  //override 获取需要服务端首次渲染的异步数据,也可以是api请求
  static async getInitialProps() {
    return null;
  }
  static title = "react ssr";
  //page组件中不要重写constructor
  constructor(props) {
    super(props);
    //如果定义了静态state,按照生命周期,state应该优先于ssrData 
   if (this.constructor.state) {
      this.state = {
        ...this.constructor.state
      };
    }
    //如果是首次渲染,会拿到ssrData
    if (props.ssrData) {
      if (this.state) {
        this.state = {
          ...this.state,
          ...props.ssrData
        };
      } else {
        this.state = {
          ...props.ssrData
        };
      }
    }
  }
  async componentWillMount() {
    //客户端运行时
    if (typeof window != "undefined") {
      if (!this.props.ssrData) {
        //静态方法,通过构造函数获取
        const data = await this.constructor.getInitialProps();
        if (data) {
          this.setState({ ...data });
        }
      }
      //设置标题
      document.title = this.constructor.title;
    }
  }
}
上一页NativeApp与H5通信原理下一页SSR的常见问题

最后更新于2个月前

这有帮助吗?

挂载在 window 上的数据
项目目录
生产环境