vue3.0 + typescript + prerender-spa-plugin实现预渲染

3,475 阅读3分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

背景

vue是一个单页面的应用,这导致一些爬虫百度无法搜索到。如果你想针对你应用的其中某些页面进行SEO优化,让他们可以被爬虫和百度搜索到,你可以进行预渲染操作,无需使用web服务器实时动态编译html,只需要在构建的时候简单的生成针对特定路由的静态html文件。优点是设置预渲染更简单,并可以将你得前端作为一个完全静态的站点。

ssr

image.png

要实现ssr,vue有一个文档Vue SSR 指南,大家可以去看看,里面讲的很详细,我在这里只简单地说一说。

问题一

为什么使用服务器端渲染 (SSR)?

1.更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。

2.更快的内容到达时间 (time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。

......

还有好多优点

问题二

为什么要预渲染?

如果你调研服务器端渲染 (SSR) 只是用来改善少数营销页面(例如 //about/demo 等)的 SEO,那么你可能需要预渲染。无需使用 web 服务器实时动态编译 HTML,而是使用预渲染方式,在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点。

如果你使用 webpack,你可以使用 prerender-spa-plugin 轻松地添加预渲染。它已经被 Vue 应用程序广泛测试 - 事实上,作者是 Vue 核心团队的成员。

实现

创建 vue3.0 项目

使用 vuecli 可以很快的创建一个项目。大概如下:

image.png

多创建一个路由页面

router/index.ts的文件中加入demo路由,如下:

import Demo from "../views/Demo.vue";
 {
    path: "/demo",
    name: "Demo",
    component: Demo,
  },

image.png

创建demo页面

在views文件夹中创建Demo.vue文件,如下:

<template>
  <div class="demo">
    <h1>This is an demo page</h1>
  </div>
</template>

image.png

引入prerender-spa-plugin

prerender-spa-plugin文档:

github: github.com/chrisvfritz…

npm: www.npmjs.com/package/pre…

image.png

有了文档我们执行下面代码:

    npm install prerender-spa-plugin -D

效果如下:

image.png

修改 vue 配置

文档里有vue的demo,不过是vue2.0的,但是我们也可以看看:

image.png

1.新建vue.config.js文件

/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
const PrerenderSPAPlugin = require("prerender-spa-plugin");

const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;

module.exports = {
  configureWebpack: {
    plugins: [],
  },
};

if (process.env.NODE_ENV === "production") {
  module.exports.configureWebpack.plugins = (
    module.exports.plugins || []
  ).concat([
    new PrerenderSPAPlugin({
      staticDir: path.join(__dirname, "dist"),
      routes: ["/", "/about", "/demo"],
      renderer: new Renderer({
        inject: {
          foo: "bar",
        },
        headless: true,
        renderAfterDocumentEvent: "render-event",
      }),
    }),
  ]);
}

注意:

文件开始要加上:/* eslint-disable @typescript-eslint/no-var-requires */,因为会报错,Require statement not part of import statement.,如下:

image.png

image.png

2.修改路由

要把router的mode改成history,也不要用懒加载模式

import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Home from "../views/Home.vue";
import About from "../views/About.vue";
import Demo from "../views/Demo.vue";

const routes: Array<RouteRecordRaw> = [
  {
    path: "/home",
    name: "Home",
    component: Home,
  },
  {
    path: "/about",
    name: "About",

    component: About,
  },
  {
    path: "/demo",
    name: "Demo",
    component: Demo,
  },
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

export default router;

3.修改App.vue

添加等待渲染,直到在文档上调度指定的事件。

import { defineComponent } from "vue";
export default defineComponent({
  name: "App",
  mounted() {
    document.dispatchEvent(new Event("render-event"));
  },
});

npm run build

看效果,demo和about文件夹里面都有index.html文件,说明我们成功了!!

image.png

思考

修改title

demo/index.html和about/index.html的title应该不一样,怎么修改? 答案1:


postProcess(context) {
        var titles = {
          "/": "Home",
          "/about": "Our Story",
          "/demo": "Demo",
        };
        context.html = context.html.replace(
          /<title>[^<]*<\/title>/i,
          "<title>" + titles[context.route] + "</title>"
        );
        return context;
      },

答案2:

postProcessHtmlfunction (context) {
          var titles = {
            '/''Home',
            '/about''Our Story',
            '/demo''Contact Us'
          }
          return context.html.replace(
            /<title>[^<]*</title>/i,
            '<title>' + titles[context.route] + '</title>'
          )
        }

效果: image.png

修改文件名和路径

怎么把demo和about文件夹的文件放到和index.html一层? 答案:

 postProcess(renderedRoute) {
        renderedRoute.route = renderedRoute.originalRoute;
        renderedRoute.outputPath = path.join(
          __dirname,
          "dist",
          (renderedRoute.route === "/" ? "home" : renderedRoute.route) + ".html"
        );
        return renderedRoute;
      },

效果,如下:

image.png

往期文章