Summary:
Because the company’s website needs to be revised, we want to separate the front and back ends, but we don’t want to affect SEO, so we decided to use Nuxt.js for server-side rendering. I didn’t know anything about nuxt.js before. After a few days of document review. Behind the direct hand began to dry, in this record of their own pit, as well as some optimization. Now the website is FreeBuf. The nuxt.js documentation is quite clear. If you don’t know about it, check the documentation yourself. I’ll skip ahead to project scaffolding and some of the issues covered in the documentation.
Question:
1. About routes
Those of you who know nuxt.js know that routes are generated from files in the page directory. But what we’re doing is changing the old version, with all the weird routes and all the different routes specifying the same page, so it’s not practical to create a directory file for each one. Here’s how I handled it:
Create an utils directory and router.js file as follows:
const { resolve } = require('path') const router = [ { name: 'vuls', path: '/vuls', component: resolve('./', 'pages/articles/web/index.vue') }, { name: 'columnId', path: '/column/:id(\\d+).html', component: resolve('./', 'pages/articles/web/_id.vue') }, { name: 'webid', path: '/web/:id', component: resolve('./', 'pages/articles/web/_id.vue') }, .... ] module.exports = routerCopy the code
Introduced in nuxt.config.js:
const zrouter = require('./utils/router') ... module.exports = { mode: 'universal', ... router: {extendRoutes (router) {const routerList = zrouter.concat(router) {const routerList = zrouter.concat(router) {const routerList = zRouter.concat (router) return routerList } } ... }Copy the code
I’m going to mention here that I ran into a bug problem because I created it in the page directory
**/column/:id ** Route points to a page. **/column/:id **/column/:id ** See above for treatment. This is why I concatenated custom routes in front of the matching priority.
2. Environment variables
Environment variables are definitely needed in development. My configuration method is as follows:
Create env.js in the root directory
const env = {
production: {
base_url: 'xxxxxxx',
host_name: 'xxxxxxx'
},
development: {
base_url: 'xxxxxx',
host_name: 'xxxxxx'
}
}
module.exports = env
Copy the code
In nuxt. Config. Js
// env = require('./env') module. Exports = {mode: 'universal', env[process.env.NODE_ENV].base_url, host_name: env[process.env.NODE_ENV].host_name, }, }Copy the code
In the package. In json
{... "scripts": { "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server --exec babel-node", "build": "nuxt build", "start": "cross-env NODE_ENV=production pm2 start server/index.js --max-memory-restart 100M -i max", }, ... }Copy the code
This can then be used in the page via process.env.xxxx.
3. Customize SVG global usage
First create the ICONS directory in assets to house the SVG
Create the svgIcon/index directory component under the Components directory
<template> <svg :class="svgClass" aria-hidden="true"> <use :xlink:href="iconName" /> </svg> </template> <script> export default { name: 'SvgIcon', props: { iconClass: { type: String, required: true }, className: { type: String, default: '' } }, computed: { iconName() { return `#${this.iconClass}`; }, svgClass() { if (this.className) { return 'svg-icon ' + this.className; } else { return 'svg-icon'; }}}}; </script> <style scoped lang="less"> .svg-icon { width: 1em; height: 1em; Vertical - align: 0.15 em. fill: currentColor; overflow: hidden; } </style>Copy the code
Create svG-icon.js under the plugins directory
Import Vue from 'Vue' import SvgIcon from '@/components/ SvgIcon 'Vue.com (' svG-icon ', SvgIcon) // Prerequest SVG component (loaded by previous SVG-sprite-loader) const req = require.context('@/assets/ ICONS ', false, /\.svg$/) const requireAll = requireContext => requireContext.keys().map(requireContext) requireAll(req)Copy the code
In nuxt. Config. Js
module.exports = {
mode: 'universal',
plugins: [
'@/plugins/svg-icon',
],
build: {
extend (config, ctx) {
const svgRule = config.module.rules.find(rule => rule.test.test('.svg'))
svgRule.exclude = [resolve(__dirname, 'assets/icons')]
config.module.rules.push({
test: /\.svg$/,
include: [resolve(__dirname, 'assets/icons')],
loader: 'svg-sprite-loader',
options: {
symbolId: '[name]'
}
})
}
}
}
Copy the code
Used in components
<svg-icon icon-class="xxx" class-name="xxx" />
Copy the code
4. File version to avoid cache
module.exports = { ... build:{ filenames: { app: ({ isDev }) => isDev ? '[name].js' : process.env.npm_package_version + '.[contenthash].js', chunk: ({ isDev }) => isDev ? '[name].js' : process.env.npm_package_version + '.[contenthash].js', css: ({ isDev }) => isDev ? '[name].css' : process.env.npm_package_version + '.[contenthash].css' }, analyze: False,// Set this to false, otherwise js cannot add version number... }}Copy the code
5. Modify the file import path and CDN
module.exports = { ... Build :{publicPath: '/freebuf/', publicPath: 'https://www.xxx',// upload the contents of. Nuxt /dist/client directory to your CDN. }}Copy the code
6. Questions about user tokens
In the past, we wanted to obtain the token through the field in cookie on the server side. In the past, it was in Zhong ‘jian, but there was a problem that users could string. Finally, we gave up the operation of obtaining token on the server side and put it on the client side
7. Route authentication
In the website need some login users can access the page, I deal with the following method
Create router.js under the plugins directory
import { getCookie } from '.. /utils/tool' const authorityRouter = ['write'] export default ({ app, store }) => { app.router.beforeEach((to, from, next) => { if (authorityRouter.includes(to.name) && ! getCookie('token')) { return window.location.href = `${process.env.host_name}/oauth` } next() }) }Copy the code
In nuxt. Config. Js
module.exports = {
mode: 'universal',
...
plugins: [
{ src: '@/plugins/router', ssr: false },
],
...
}
Copy the code
Remember this only runs on the server side.
8. About server Error
Since nuXT is used for server rendering, asyncData must be used. Server error will inevitably occur if errors are not captured during Ajax request in asyncData.
My treatment is as follows:
Start by creating an error page in your layouts
<template> <div class="container"> <div class="container-top" style="background-image:url('/images/404.png')" /> <div Class ="container-bottom"> <p>404</p> <nuxt-link to="/"> <a-button type="primary"> </div> </template> <script> export default { props: ['error'], layout: Head () {return {title: '404'}}} </script>Copy the code
In asyncData
async asyncData ({ route, error }) { const [listData1, listData2, listData3, listData4] = await Promise.all([ userCategory().catch((e) => { return error({ statusCode: 404 }) }), categoryKeyword().catch((e) => { return error({ statusCode: 404 }) }), categorylist().catch((e) => { return error({ statusCode: 404 }) }), columnHot().catch((e) => { return error({ statusCode: 404 }) }) ]) return { userDataList: listData1.data, seoData: listData2.data, dataLists: listData3.data, homeColumnData: Listdata4. data}} // async asyncData ({route, error}) {const [listData1, listData2, listData3, listData4] = await Promise.all([ try{ userCategory() categoryKeyword() categorylist() columnHot() } catch{ return error({ statusCode: 404 }) } ]) return { userDataList: listData1.data, seoData: listData2.data, dataLists: listData3.data, homeColumnData: listData4.data } }Copy the code
This will point you to a custom error page if you catch an error, or you can define different page contents with different statusCode values
9. vuex
For state management, nuxt.js is integrated as well.
Create userinfo.js under the store directory
export const state = () => ({
userInfo: {},
})
export const mutations = {
setUserInfo (state, text) {
state.userInfo = text
},
deleteUserInfo (state) {
state.userInfo = ''
},
}
Copy the code
Use:
/ / read the computed: {userInfoData () {return this. $store. State. The userInfo. The userInfo}} / / write const userData = {} Use this. Codestore.com ('userInfo/setUserInfo', userData)Copy the code
Because of the nuxt.js integration, vuex can also be manipulated on the server side
To optimize the
Code 1.
- UI components introduce custom themes on demand
- Lazy loading of images
- Global CSS
2. The file
const CompressionPlugin = require('compression-webpack-plugin') const TerserPlugin = require('terser-webpack-plugin')module.exports = { ... Content-encoding :gzip new CompressionPlugin({test: / \. Js $| \. HTML $| \. CSS /, / / matching filename threshold: 10240, / / to compression of the data of more than 10 KB deleteOriginalAssets: False // Whether to delete the original file}), // Block some warnings and console debugger new TerserPlugin({terserOptions: {compress: {warnings: }}})], // large file fragmentation optimization: {splitChunks: {minSize: 10000, maxSize: 250000 } } } }Copy the code
3. The cache
The cache I use is lru-cache to view the document specifically, and here I will mention the pit that appears.
// Check whether the interface needs to be cached. Is there a cache instance. Interceptors. Request. Use ((config) = > {if (config. The cache) {const {params = {}, Data = {}} = config key = md5(config.url + json.stringify (params) + json.stringify (data)) // Remember config.url === Cached.get (key).config.url; // The key is unique. If (cached.has (key) && config.url === cached.get (key).config.url) {return promise.reject (cached.get (key)) } else {return config}} else {return config}} import instance from '@/plugins/service.js' export const getHotList = (params) => { return instance.get('/index/index', { params, cache: true, time: 5000})} / / save the cache instance. The interceptors. Response. Use (/ / request success (res) = > {the if (res) status = = = 200) {/ / code if according to actual situation (res.data.code ! Reject (res.data)} else {if (res.config.cache) {// Set cached. set(key, res, res.config.time) } return Promise.resolve(res.data) } } else { return Promise.reject(res) } }, // Request failed (error) => {if (error) {if (error.status === 200) {return promise.resolve (error.data)} else {return Promise.reject(error) } } }Copy the code
First of all, let me explain how it’s written:
Why AM I using return promise.reject (cached.get (key)) in axios interceptor Request?
The reason is that I can’t run return promise.resolve () directly in the interceptor
Return promise.reject (cached.get (key))
The interface response interceptor checks whether the cached data is from the cache and returns the data directly, without affecting the encapsulated interface.
The odd thing is that the data string problem, the value is evaluated by key but somehow the returned data is the data of another interface, it feels weird. Later, a condition is added to determine whether there is a cache, that is, whether the current interface is consistent with the interface in the cache, which barely solves this problem.
Page caching:
Create pagecache.js in the root directory
import instance from './plugins/service.js' const cachePage = require('./globalCache') const cacheUrl = require('./utils/urlCache') export default async function (req, res, next) { let isUpdata = false const pathname = req.originalUrl const urlData = cacheUrl(pathname) if (pathname === '/') { console.log(parseInt(new Date().getTime() / 1000)) await instance.get('xxxxxxxx', { time: ParseInt (new Date().getTime() / 1000)}).then((res) => {if (res.data.home) {console.log(' home update - need cache - set cache content ', req.originalUrl) isUpdata = true // cachePage.set('/', null) } console.log(res) }).catch((err) => { console.log(err) }) } const existsHtml = cachePage.get(pathname) if (existsHtml && ! IsUpdata) {console.log(' fetch cached Content ', req.originalURL) res.writehead (200, {' content-type ': 'text/ HTML; Charset =utf-8'}) return res.end(existsHtml, 'utf-8')} else {res.original_end = res.end // Override res.end if (! CacheUrl (pathName)) {console.log(' No cache - no cache required ', Req.originalurl) return next()} else {res.end = function (data) {if (res.statuscode === 200) {// Set cache Console. log(' Cache required - set cache contents ', req.originalURL) cachePage.set(pathname, data, urldata.time)} res.original_end(data, 'utf-8') } } } next() }Copy the code
Create urlcache.js in utils
const cacheUrl = function (url) { const list = new RegExp('/[articles | abc]+(/[a-z]*)? ') const nuber = new RegExp('[0-9]') if (url === '/') { return { name: '/', time: 1000 * 60 } } else if (nuber.test(url)) { return false } else if (list.test(url)) { return { name: url, time: 1000 * 60 * 5 } } else { return false } } module.exports = cacheUrlCopy the code
The purpose of this file is to control how many pages need to be cached for different times
Create globalCache.js in the root directory
Module. Exports = cachePage const LRU = require('lru-cache') const cachePage = new LRU({Max: 10 // set maximum cache number}) module.exports = cachePageCopy the code
Finally introduced in nuxt.config.js
module.exports = {
...
serverMiddleware: [
'./pageCache'
],
...
}
Copy the code
One issue I want to mention here is the issue of checking for updates
**cachePage.set(‘/’, null)** The cachepage.set (‘/’, null) I commented out above. Because the cache contents are shared, if the value is set to NULL, the result may be null when the user accesses the cache. Therefore, the method of variable is used to control the following, so as to avoid the error caused by the user taking null
4. The deployment is running
Since the runtime was run through Node, some problems occurred after the deployment of this project. Both the test server and the online pre-release environment were OK. However, server error frequently appeared on the home page when the project was finally launched.
Finally by looking at the server:… The CPU is actually more than 100%. And that’s just one thread running. I’d be surprised if I suddenly realized this.
Then I asked the operation and maintenance to install PM2, and I changed the configuration:
{... "scripts": { "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server --exec babel-node", "build": "nuxt build", "start": "cross-env NODE_ENV=production pm2 start server/index.js --max-memory-restart 100M -i max", }, ... }Copy the code
–max-memory-restart 100M sets node to restart when memory exceeds 100 MB. Avoid memory overflow problems.
Below is the test server with only two cores. Now you can see that there are two processes.
The rest of the project is basically stable…