This article mainly introduces the use of prerender-spa-plugin to pre-render the front-end code.
There is a certain difference between pre-rendering (SSG) and server-side (SSR) rendering. If you want to know, please see: https://segmentfault.com/a/1190000023469150 .
background
Because the previous website was developed using Vue, this front-end JavaScript rendering development model is very unfriendly to search engines, and there is no way to capture effective information. Therefore, in order to perform SEO, we need to pre-render the page.
Pre-rendering is more suitable for static or little changed pages, and can render most of the content on the page through a static rendering before deployment. In this way, when the search engine is crawling, it can crawl to the relevant content information.
status quo
The current situation of the official website of Shangqitong is listed as follows:
- The technology stack uses Vue, the scaffolding uses vue-cli, and the JavaScript front-end rendering solution (this solution has no requirements for the technology stack and is compatible with all solutions)
- The publishing tool uses the company's tools. During the packaging process, HTML resources are transferred to the A domain name, and CSS, JS, Image and other resources are transferred to the B domain name.
Target
It is hoped that through pre-rendering, the page can carry enough information when JavaScript is not executed for the first visit, that is, the content rendered by JavaScript can be rendered into HTML in advance.
The release expects not to make too many changes.
plan
Our plan this time mainly uses prerender-spa-plugin, a webpack plug-in to achieve.
Its main principle is to start the browser, grab the HTML after rendering, and then replace the original HTML.
We need to implement pre-rendering, then we need to complete the following things:
- Plug-in introduction and configuration.
- Local verification.
- Transform the packaging and construction process.
- Online verification.
Below, let's talk about how we do this one by one.
Plug-in introduction and configuration
First, we need to introduce a pre-rendering plugin and execute the command:
mnpm i prerender-spa-plugin -D
In addition to installing the plug-in itself, this command relies on puppeteer, and then puppeteer relies on the landing chromium, so in the end we actually need to install a chromium in the dependency.
If you install puppeteer very slowly or often fail, you can refer to the method in this document: https://brickyang.github.io/2019/01/14/Domestic download and installation-Puppeteer- method / , specify puppeteer download Mirror.
After the installation is complete, we can add the corresponding configuration to the webpack configuration file.
If you are also using vue-cli, then the configuration we need to add is in vue.config.js. If you modify the configuration of webpack directly, the method is similar.
Let's take the modification of vue.config.js as an example:
const PrerenderSPAPlugin = require('prerender-spa-plugin');
module.exports = {
...,
configureWebpack: {
...,
chainWebpack: config => {
config.plugin('prerender').use(PrerenderSPAPlugin, [
{
staticDir: path.join(__dirname, 'build'),
routes: [
'/',
'/product',
'/case',
'/about',
'/register',
],
renderer: new Renderer({
headless: true,
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
// 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
renderAfterDocumentEvent: 'render-event',
}),
},
]);
}
}
}
Because we used webpack-chain in the project, our syntax is similar to the chain call method above. If you modify it directly, it is to use Vue's original way of modifying the configuration.
Let me briefly introduce to you the meaning of some of the above configurations:
- staticDir: This refers to the directory where the pre-rendered files are output.
- routes: This refers to the routes that need to be pre-rendered. It should be noted here that vue's hash routing strategy has no way to pre-render, so if you want to pre-render, you need to change to history routing, and then after pre-rendering, it will become multiple HTML files, each with the full amount The routing function is just different from the default route.
- renderer: This is the configuration of puppeteer that can be passed in. Let me talk about the following configurations I have used:
-headless: Whether to use headless mode for rendering, it is recommended to select true.
-executablePath: Specify the path of chromium (it can also be chrome). This configuration needs to be specified in talos. The chrome address in talos is /usr/bin/google-chrome by default.
-renderAfterDocumentEvent: This means that after which event is triggered, pre-rendering is performed. This event needs to be triggered by dispatchEvent in the code so that you can control the timing of pre-rendering. Generally, we trigger in the mounted hook of the outermost component. If you have other requirements, you can also specify it yourself.
You can see more of the plug-in official document .
After the development is complete, we can build it locally to see if we can generate code that meets our expectations.
Vue.config.js specifies publicPath causing pre-rendering failure
If you are the same as my project, pass in publicPath in vue.config.js to specify the third-party CDN domain name, and then CSS, JavaScript, Image and other resources will be transferred to different domain names. The similar configuration is as follows:
module.exports = {
...,
publicPath: `//awp-assets.cdn.net/${projectPath}`,
...,
};
If there is no pre-rendering, this solution will be uploaded to different CDN domain names after the packaging is completed, and there is no problem with online access.
However, locally, CSS and JS resources have not been uploaded to the CDN at this time, and the browser cannot load the corresponding resources for page rendering. This will cause the local pre-rendering to fail.
In order to solve this problem, there are two solutions.
- [Recommended] Adjust the packaging strategy and upload non-HTML resources to the same CDN domain name. In this case, we can use relative paths to access these resources without passing the new domain name to publicPath, so when we build locally You can access these values. This is a more reliable and reasonable method and is more recommended.
- (If the above method is really impossible to achieve, then you can consider this solution) Before pre-rendering, the resources can be accessed locally through relative paths. At this time, replace the resource file address in the HTML with the replacement method, and then pre-render. Replace it after rendering. This method is more hacky, but it is indeed effective after actual verification. The specific approach is to write a simple webpack plugin by yourself.
First of all, we need to install a new NPM package to replace the content in the file (you can also write the regular rules yourself, but it will be more convenient to use this), the specific commands are as follows:
mnpm i replace-in-file
After installation, we need to add two webpack plugins to act on the two hook nodes of afterEmit and done respectively. If you want to understand why these two hook nodes are, then you can read the development chapter of the webpack plugin.
const replace = require('replace-in-file');
let publicPath = `//awp-assets.cdn.net/${projectPath}`;
// 第1个替换插件,主要是将原先打包过程中带有CDN域名的路径替换成相对路径
function ReplacePathInHTMLPlugin1(cb) {
this.apply = compiler => {
if (compiler.hooks && compiler.hooks.afterEmit) {
compiler.hooks.afterEmit.tap('replace-url', cb);
}
};
}
function replacePluginCallback1() {
replace({
files: path.join(__dirname, '/build/**/*.html'),
from: new RegExp(
publicPath.replace(/([./])/g, (match, p1) => {
return `\\${p1}`;
}),
'g'
),
to: '',
})
.then(results => {
console.log('replace HTML static resources success', results);
})
.catch(e => {
console.log('replace HTML static resources fail', e);
});
}
// 第2个替换插件,主要是将预渲染后的HTML文件中的相对路径替换成带有CDN域名的路径
function ReplacePathInHTMLPlugin2(cb) {
this.apply = compiler => {
if (compiler.hooks && compiler.hooks.done) {
compiler.hooks.done.tap('replace-url', cb);
}
};
}
function replacePluginCallback2() {
replace({
files: path.join(__dirname, '/build/**/*.html'),
from: [/href="\/css/g, /href="\/js/g, /src="\/js/g, /href="\/favicon.ico"/g],
to: [
`href="${publicPath}/css`,
`href="${publicPath}/js`,
`src="${publicPath}/js`,
`href="${publicPath}/favicon.ico"`,
],
})
.then(results => {
console.log('replace HTML static resources success', results);
})
.catch(e => {
console.log('replace HTML static resources fail', e);
});
}
The above code is the two webpack replacement plug-ins and corresponding callback functions that we need to add. Next, let's see how to configure it in webpack.
module.exports = {
publicPath,
outputDir,
crossorigin: 'anonymous',
chainWebpack: config => {
config.plugin('replaceInHTML').use(new ReplacePathInHTMLPlugin1(replacePluginCallback));
config.plugin('prerender').use(PrerenderSPAPlugin, [
{
staticDir: path.join(__dirname, 'build'),
// 我们应该只会使用根路径,因为是hash路由,所以其他页面预渲染没有意义,因此不进行预渲染
routes: ['/'],
renderer: new Renderer({
headless: true,
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
// 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
renderAfterDocumentEvent: 'render-event',
}),
},
]);
config.plugin('replaceInHTML2').use(new ReplacePathInHTMLPlugin2(replacePluginCallback2));
}
Our first replacement plug-in needs to be executed before the pre-rendering plug-in. Before the pre-rendering plug-in is executed, the address of the resource in the HTML is replaced with the relative path of the local; the second one needs to be executed after the replacement, which will pre-render The relative path in the back-end resource is replaced with the CDN address.
Through these two plug-ins, we can complete the pre-rendering by replacing the path before pre-rendering, and then completing the replacement after pre-rendering to ensure online availability.
Local verification
Through the above method, we should have obtained a pre-rendered HTML, and then we have to verify whether the HTML meets expectations.
The simpler way of verification is to directly access the HTML file, or start an HTTP static resource service to verify.
For verification, you can use curl to make a request. In this case, JavaScript will not be executed, and you can see what the HTML source file is.
FAQ
- In the case of a relatively low chrome version (such as v73), it will prompt the rendering failure?
This is because the version of chrome is too low, causing pre-rendering to fail. The solution is to upgrade the chrome/chromium version to the latest (currently v93 no problem) version.
Summarize
If we need to implement SSG (static site generation), then we can use the prerender-spa-plugin plug-in to do it, this plug-in can start chromium locally to grab HTML content, and then write it back to the HTML file, if we need to For processing the static resource files, we can use the replacement plug-in to replace the content before and after the processing to meet our demands.
Although directly replacing the compressed code looks effective, this strongly relies on the compression algorithm and content sequence. It is strongly not recommended to directly modify and replace the compressed file with a script. It is best to handle it in the done hook callback of webpack.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。