vue的SSR方案探讨

最近有一个项目要求SEO,于是摸索了一下vue的ssr方案,顺便总结一下。

先简单说一下ssr(Server-Side Rendering),官方是这样说的:

Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序

其实也就是说把在客户端生成的HTML节点,改成在服务端生成

这样做的好处是,可以加快首屏的渲染(加快内容到达时间),对seo更友好。缺点是开发条件所限,需要更多的服务器端负载等,详情内容请移步官网https://ssr.vuejs.org/zh

本文总结了四种ssr方案,各有利弊

一. vue官网SSR方案

先上一张构建流程图

大概就是分别定义客户端入口(client entry)和服务端入口(server entry),然后通过webpack分别打包生成 server bundle 和client bundle

server bundle 用于服务器端渲染(SSR), client bundle发送给浏览器,用于混合静态标记

服务端的渲染,需要用到‘vue-server-renderer’插件进行处理,同时还要对vue入口文件和vue router 进行改造,具体实现请看demo,这里不再展开。

https://github.com/chenpeiguang/vue-cli-ssr-demo

几个需要注意的地方

1.不要把有副作用的代码放到created中,比如调用浏览器的api,因为created钩子会在服务端渲染的时候触发,避免找不到相关api报错

2.最好不要使用第三方的插件(需要调用浏览器api),比如vue toast,原因同上

3.官方提到的

二. nuxt.js

nuxt是vue的ssr框架,为ssr提供整套的解决方案。可以很好的解决上面第一种存在的一些问题,缺点就是增加学习成本,具体实现看官方教程,https://zh.nuxtjs.org/guide,一笔带过。

三. 利用puppeteer

Google Chrome 团队官方的无界面(Headless)Chrome 工具,它是一个 Node 库,提供了一个高级的 API 来控制 DevTools协议上的无头版 Chrome。

思路就是,利用puppeteer抓取SPA并生成预先呈现的内容,也就是ssr

实现:

1.根目录新建app.js,作为静态服务器,为不需要ssr的静态资源提供访问

    const Koa = require('koa')
    const app = new Koa()
    const server = require('koa-static')
    const cors = require('@koa/cors')

    app.use(cors())

    app.use(server(`${__dirname}/dist`, {
      maxage: 7200 * 1000,
    }))

    const port = process.env.PORT || 5001
    app.listen(port)
    console.log(`is working on port ${port}`)

2.根目录新建server.js,通过puppeteer进行ssr

const Koa = require('koa');
const app = new Koa();
const server = require('koa-static');
const puppeteer = require('puppeteer');
const { URL } = require('url');
const cors = require('@koa/cors');

const RENDER_CACHE = new Map();

app.use(cors());

app.use(async (ctx, next) => {
  const url = new URL(ctx.request.href);
  url.hostname = 'localhost'
  url.port = process.env.PORT || 5001;
  console.log('url: ', url.href);
  if (!isNeedSsrPath(url.pathname)) {
    console.log('is not need ssr path: ', url.pathname);
    await next();
  } else {
    const { html, ttRenderMs } = await ssr(url.href);
    ctx.set('Server-Timing', `Prerender;dur=${ttRenderMs};desc="Headless render time (ms)"`);
    ctx.body = html;
  }
});

app.use(server(`${__dirname}/dist`, {
  maxAge: 7200 * 1000,
}));

const port = process.env.SSR_PORT || 5009;
app.listen(port);
console.log(`ssr is working on port ${port}`);

async function ssr (url) {
  if (RENDER_CACHE.has(url)) {
    return {html: RENDER_CACHE.get(url), ttRenderMs: 0};
  }

  const start = Date.now();

  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  try {
    await page.goto(url, {waitUntil: ['domcontentloaded', 'load', 'networkidle0', 'networkidle2']});
  } catch (err) {
    console.error(err);
  }

  const html = await page.content(); // serialized HTML of page DOM.
  await browser.close();

  const ttRenderMs = Date.now() - start;
  console.info(`Headless rendered page in: ${ttRenderMs}ms`);

  RENDER_CACHE.set(url, html);

  return {html, ttRenderMs};
}

function isNeedSsrPath (path) {
  let flag = true;
  const dontNeedSsrPathArray = ['/static',  '/js', '/css'];
  dontNeedSsrPathArray.forEach(item => {
    if (path.startsWith(item)) {
      flag = false;
    }
  });

  return flag;
}

这种方案的好处,就是不像上面两种方案,需要改动原来的代码,在项目的任何一个阶段,都可以很好的进行ssr改造。并且可以很好的控制哪些页面需要ssr,哪些页面不需要ssr。

再想一下,其实这种方案也是有缺点的,无论爬虫还是用户正常访问都需要server.js入口访问,遇到静态资源或者不需要ssr的页面,还需要转到另一个静态服务器。增加访问成本。

这里还可以优化一下。通过请求,判断一下爬虫,如果是爬虫,就按照上面的使用puppeteer进行ssr ,返回html结构。如果是正常用户,可以直接build一个版本访问。

就是区分爬虫

标签: ssr

添加新评论