尽管 Vue 的 SSR 速度相当快,但由于创建组件实例和虚拟 DOM 节点的成本,它无法与纯基于字符串的模板的性能相匹配。在 SSR 性能至关重要的情况下,明智地利用缓存策略可以极大地缩短响应时间并减少服务器负载。

缓存能够更快的将内容发送给客户端,提升 web 应用程序的性能,同时减少服务器的负载。

页面缓存

官方文档中介绍的那样,对特定的页面合理的应用 micro-caching 能够大大改善服务器处理并发的能力(吞吐率 RPS)。

但并非所有页面都适合应用 micro-caching 缓存策略,我们可以将资源分为三类:

  • 静态资源:如 jscssimages 等。
  • 用户特定的动态资源:不同的用户访问相同的资源会得到不同的内容。
  • 用户无关的动态资源:任何用户访问该资源都会得到相同的内容,但该内容可能在任意时间发生变化,如博客文章。

只有“用户无关的动态资源”适合应用 micro-caching 缓存策略。

安装依赖:

  1. npm i lru-cache

server.js

  1. const express = require('express')
  2. const fs = require('fs')
  3. const { createBundleRenderer } = require('vue-server-renderer')
  4. const setupDevServer = require('./build/setup-dev-server')
  5. const LRU = require('lru-cache')
  6. const cache = new LRU({
  7. max: 100,
  8. maxAge: 10000 // Important: entries expires after 1 second.
  9. })
  10. const isCacheable = req => {
  11. console.log(req.url)
  12. if (req.url === '/posts') {
  13. return true
  14. }
  15. }
  16. const server = express()
  17. server.use('/dist', express.static('./dist'))
  18. const isProd = process.env.NODE_ENV === 'production'
  19. let renderer
  20. let onReady
  21. if (isProd) {
  22. const serverBundle = require('./dist/vue-ssr-server-bundle.json')
  23. const template = fs.readFileSync('./index.template.html', 'utf-8')
  24. const clientManifest = require('./dist/vue-ssr-client-manifest.json')
  25. renderer = createBundleRenderer(serverBundle, {
  26. template,
  27. clientManifest
  28. })
  29. } else {
  30. // 开发模式 -> 监视打包构建 -> 重新生成 Renderer 渲染器
  31. onReady = setupDevServer(server, (serverBundle, template, clientManifest) => {
  32. renderer = createBundleRenderer(serverBundle, {
  33. template,
  34. clientManifest
  35. })
  36. })
  37. }
  38. const render = async (req, res) => {
  39. try {
  40. const cacheable = isCacheable(req)
  41. if (cacheable) {
  42. const html = cache.get(req.url)
  43. if (html) {
  44. return res.end(html)
  45. }
  46. }
  47. const html = await renderer.renderToString({
  48. title: '拉勾教育',
  49. meta: `
  50. <meta name="description" content="拉勾教育">
  51. `,
  52. url: req.url
  53. })
  54. res.setHeader('Content-Type', 'text/html; charset=utf8')
  55. res.end(html)
  56. if (cacheable) {
  57. cache.set(req.url, html)
  58. }
  59. } catch (err) {
  60. res.status(500).end('Internal Server Error.')
  61. }
  62. }
  63. // 服务端路由设置为 *,意味着所有的路由都会进入这里
  64. server.get('*', isProd
  65. ? render
  66. : async (req, res) => {
  67. // 等待有了 Renderer 渲染器以后,调用 render 进行渲染
  68. await onReady
  69. render(req, res)
  70. }
  71. )
  72. server.listen(3000, () => {
  73. console.log('server running at port 3000.')
  74. })

Gzip 压缩

注意事项:

  • 默认的过滤器功能使用 compressible 模块来确定 res.getHeader(’Content-Type’)是否可压缩。

组件级别缓存

参考链接