After reading the XX Alliance recently, I also want to access advertising in my website, but my website is a single-page application developed by VUE, which is not very friendly to SEO. Naturally, access advertising will also suffer some losses. Also, SEO is an important metric for a blogging system, so I rewrote one side of the site in NUXT and changed it to SSR (server-side rendering).

Effect: zhangjinpei. Cn /

Github:github.com/love-peach/…

Initialize the project

Initialize the project using the scaffolding tool create-nuxt-app provided by Nuxt.js

run

NPX create-nuxt-app < project name > yarn create nuxt-app < project name >Copy the code

Yarn Create See Yarn Create

Yarn global add create-nuxt-app create-nuxt-app < project name >Copy the code

Project configuration

The directory structure

. ├ ─ ─. Editorconfig ├ ─ ─ the env ├ ─ ─ the eslintrc. Js ├ ─ ─ the git / ├ ─ ─ the gitignore ├ ─ ─ the nuxt / / / nuxt automatically generated, ├─.prettierrc ├─ readme.md ├─ Assets for organizing uncompiled static resources like LESS, SASS or JavaScript ├─ Components // / For organizing applications Vue. Js components. │ ├─ Jsconfig. json │ ├─ layouts │ // middleware │ middleware │ for application │ The nuxt.config.js // file is used to organize the personalized configuration of the nuxt.js application to override the default configuration. ├─ package.json ├─ pages/ // for organizing application routes and views and automatically generating the appropriate route configuration ├─ // for organizing Javascript plug-ins that need to be run before the root vue.js application is instantiated. ├─ server/ ├─ static/ / ├─ stylelin.config.js ├─ tsconfig.json ├─ yarn. LockCopy the code

Run the project

yarn run dev
Copy the code

In your browser, open http://localhost:3000

Set up front page

After the project is launched, we can develop the front end page.

Add a page

In NUXT, most routes can be generated automatically through the Pages/directory, for example:

└── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ─Copy the code

The following routes are generated:

router: {
  routes: [{name: 'article'.path: '/article'.component: 'pages/article/index.vue'
    },
    {
      name: "article-category"
      path: "/article/:category".component: 'pages/article/_category/index.vue'}, {name: "article-detail-articleId"
      path: "/article/detail/:articleId".component: 'pages/article/detail/_articleId.vue'}}]Copy the code

Layout of the layout

Since my blog has several sections, article, Resource, Moive, ebook, Admin and User, there will be several layouts. In the Layout/directory, add the corresponding layout files. The sketch is as follows:

From the sketch, you can see that default and User have the same structure, header,footer, and content, so extract them into the component AppLayout. Here is the content of the default and User layout:

<template>
  <AppLayout>
    <nuxt />
  </AppLayout>
</template>

<script>
import AppLayout from '@/components/framework/app-layout/index.js';

export default {
  name: 'AppLayoutDefault'.components: {
    AppLayout,
  },
};
</script>
Copy the code
<template>
  <AppLayout>
    <div class="z-container">
      <div class="user-page-wrap">
        <div class="user-page-side">
          <div class="hidden-xs">
            <UserBrief />
          </div>
          <Card :padding="0" style="margin-bottom: 0;">
            <UserMenu></UserMenu>
          </Card>
        </div>
        <div class="user-page-main">
          <nuxt />
        </div>
      </div>
    </div>
  </AppLayout>
</template>

<script lang="ts">
import AppLayout from '@/components/framework/app-layout/index.js';

import Card from '@/components/base/card/';
import UserMenu from '@/components/framework/user-menu.vue';
import UserBrief from '@/components/framework/user-brief.vue';

export default {
  name: 'AppLayoutUser'.components: {
    AppLayout,
    Card,
    UserMenu,
    UserBrief,
  },
  head() {
    return {
      title: 'User center'}; }};</script>
Copy the code

Note That the name value of the file in the layout/ directory cannot be the same, or an error will be reported.

Use the layout

If the Layout field is not indicated as follows, the default layout will be used.

export default Vue.extend({
  layout: 'admin'});Copy the code

Component, page component

The common components we know are in the conponents/ directory, but where are the components in the page? Placing it in the corresponding folder in the Pages directory is definitely not a good idea because it will render the component to the page and generate the route.

I recommend putting it in the componets/ directory as well, and then classifying the different components by directory, as follows

├─ ├─ Bass Exercises, ├─ ├─ Bass Exercises, ├─ Bass Exercises, ├─ Bass Exercises, ├─ Bass Exercises, ├─ Movie ├─Copy the code

Where is CSS js IMG?

Generally put in assets/ directory.

If you don’t need Webpack to do the build processing, you should put it in static/, which maps to the application root /.

Global style

First, global-style files can be placed in assets/ and then imported in nuxt.config.js

module.exports = {
  css: ['@/assets/css/grid.less'.'@/assets/css/reset.less'],}Copy the code

Individual styles can be introduced in a file and then configured:

// assets/css/index.less
@import './variables.less';
@import "./clearfix.less";
@import './reset.less';
@import './animate.less';
@import "./grid.less";
@import './common.less';
Copy the code
module.exports = {
  css: ['@/assets/css/index.less'],}Copy the code

Less global variable

If you are preprocessing styles with less, you may need to use variables, but you don’t want to introduce variable files into every page. How do you do that?

Start by defining the variable.less file:

@colorPrimary: #2d8cf0;
@colorPrimaryLight: lighten(@colorPrimary.10%);
@colorPrimaryDark: darken(@colorPrimary.10%);
@colorPrimaryFade: fade(@colorPrimary.10%);
Copy the code

Then configure nuxt.config.js as follows:

module.exports = {
  build: {
    // extend(config, ctx) {},
    loaders: {
      less: {
        lessOptions: {
          modifyVars: getLessVariables(resolve('assets/css/variables.less')),
          javascriptEnabled: true,},},},},}Copy the code

The getLessVariables function is implemented as follows:

const path = require('path');
const fs = require('fs');

function resolve(dir) {
  return path.join(__dirname, dir);
}

function getLessVariables(file) {
  const themeContent = fs.readFileSync(file, 'utf-8');
  const variables = {};
  themeContent.split('\n').forEach(function(item) {
    if (item.includes('/ /') || item.includes('/ *')) {
      return;
    }
    const _pair = item.split(':');
    if (_pair.length < 2) return;
    const key = _pair[0].replace('\r'.' ').replace(The '@'.' ');
    if(! key)return;
    const value = _pair[1]
      .replace('; '.' ')
      .replace('\r'.' ')
      .replace(/^\s+|\s+$/g.' ');
    variables[key] = value;
  });
  return variables;
}
Copy the code

Global filter

In the plugins/ directory, create a new filters.js:

import Vue from 'vue';
import dayjs from 'dayjs';

export function dateFormatFilter(date, fmt) {
  if(! date) {return The '-';
  } else {
    returndayjs(date).format(fmt); }}const filters = {
  dateFormatFilter,
};

Object.keys(filters).forEach(key= > {
  Vue.filter(key, filters[key]);
});
export default filters;
Copy the code

Then, in nuxt.config.js,

module.exports = {
    plugins: ['~/plugins/filters.js'],}Copy the code

Custom instruction

As with defining global filters, you need to operate in plugins.

In the plugins/directive/focus directory, add focus.js

import Vue from 'vue';
const focus = Vue.directive('focus', { inserted(el) { el.focus(); }});export default focus;
Copy the code

Then, in nuxt.config.js,

module.exports = {
  plugins: [
    '~/plugins/filters.js', {src: '~/plugins/directive/focus/index.js'.ssr: false},],}Copy the code

Head Dynamically sets the page title

In douban movie and ebook columns of my blog, I used a lot of external pictures, and they did the anti-theft processing, without configuration, the situation of 403 will appear, and the pictures are not allowed to access. The idea is to add a meta tag and set the referrer.

When I had a single page, I configured it in public/index.html so that every page had this tag, and I didn’t want to do that.

Nuxt.js uses vue-meta to update the application’s Head tags and HTML attributes.

We can configure this property in the page to change the title of the page, or dynamically add some meta tags. In the head, we can use this keyword to retrieve component data.

head() {
  return {
    title: `The ${this.blogResult.title}Details page `.meta: [{ hid: 'ebook-home referrer'.name: 'referrer'.content: 'never'}]}; },Copy the code

Alternatively, nuxt.config.js is used to configure the head information globally, so it is best to provide a unique HID number when configuring meta tags.

asyncData

Because of the USE of SSR, when the browser requests the page, the data in the page will be rendered well, return a complete HTML content.

The asyncData method is called before each load of the component (page component only). It can be invoked before a server or route is updated. When this method is called, the first parameter is set to the context object of the current page, and you can use the asyncData method to fetch data and return it to the current component.

export default {
  data () {
    return { blogList: []}; },async asyncData({ app }: ctxProps) {
    const params = { page: 1.limit: 10 };
    const res = await app.$myApi.blogs.index(params);
    return {
      blogList: res.result.list,
      pageTotal: res.result.pages,
      itemTotal: res.result.total, }; }},Copy the code

fetch

Sometimes, we just want to pull the latest data when the page comes in, not modify the value in the component. In this case, we can use the fetch method.

If a page component sets the fetch method, it will be called before each load of the component (on the server or before switching to the destination route). Fetch is called before the component is initialized.

Similar to the asyncData method, except that it does not set the component’s data.

async fetch({ store }: ctxProps) {
  await store.dispatch('common/requestCategoryList');
},
Copy the code

Decoupling API

It is recommended that the interface be RESTFUL, so an interface is basically like this

import request from '@/utils/request';

export default {
  GetCategory: (params, options) = > request.get('/categories', params, options),
  PostCategory: (params, options) = > request.post('/categories', params, options),
  PutCategory: (params, options) = > request.put(`/categories/${params.categoryId}`, params, options),
  DeleteCategory: (params, options) = > request.delete(`/categories/${params.categoryId}`, params, options),
}
Copy the code

However, each time the request method needs to be mapped, such as axios, the wrapped request.

Refer to this article to gracefully decouple your apis: organize and decouple the apis you call in NuxtJs

The proxy agent

Requesting interfaces, which inevitably use proxies, is also easy to configure in NUxT.

First, install dependencies (don’t seem to install, if not, you are installing)

yarn add @nuxtjs/proxy
Copy the code

Then, add modules to nuxt.config.js and configure,

module.exports = {
  modules: ['@nuxtjs/proxy'].proxy: {
    '/api/': {
      target: process.env.NODE_ENV === 'production' ? 'http://localhost:3000/' : 'http://localhost:3000/'.// target: process.env.NODE_ENV === 'production' ? 'http://zhangjinpei.cn' : 'http://localhost:3000/',
      changeOrigin: true,},'/douban/': {
      target: 'http://api.douban.com/v2'.changeOrigin: true.pathRewrite: {
        '^/douban': ' ',}},'/ebookSearch/': {
      target: 'http://www.shuquge.com/search.php'.changeOrigin: true.pathRewrite: {
        '^/ebookSearch/': ' ',},},},};Copy the code

Previously, in a single page mode, local development, with a proxy like the above, after the launch of the need to configure the Nginx proxy. But in NUXT, you don’t need to do interface-specific proxies anymore.

My online NG agent is as follows:

upstream server_host { server localhost:3000; } server { listen 80; server_name zhangjinpei.cn; rewrite ^(.*)$ https://$host$1 permanent; gzip on; Gzip_http_version 1.0; gzip_proxied any; gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/vnd.ms-fontobject font/ttf font/opentype font/xwoff image/svg+xml; } #server { # listen 80; # server_name ssr.zhangjinpei.cn; # location / { # proxy_set_header X-Real-IP $remote_addr; # proxy_set_header Host $http_host; # proxy_pass http://127.0.0.1:8000; # } #} server { listen 443 ssl; server_name zhangjinpei.cn; root /root/zhangjinpei/nuxt-ts-blog/; index index.html index.htm; ssl_certificate /xxx/xx; ssl_certificate_key /xxx/xx; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:! NULL:! aNULL:! MD5:! ADH:! RC4; Ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; #location / { # root /root/zhangjinpei/blog-front/dist; # try_files $uri /index.html; #} location / {proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Nginx-Proxy true; proxy_cache_bypass $http_upgrade; Proxy_pass http://127.0.0.1:8000; } #location /api/ { # proxy_pass http://server_host/api/; # poxy_set_header Host $host; # proxy_set_header X-Real-IP $remote_addr; # proxy_set_header X-Forwarded-For $remote_addr; # proxy_set_header X-Forwarded-Host $host; # proxy_set_header X-Forwarded-Server $host; #} #location /douban/ { # proxy_pass http://api.douban.com/v2/; #} #location /douban/movie/ { # proxy_pass http://api.douban.com/v2/movie/; #} #location /doubanOld/ { # proxy_pass https://movie.douban.com/; #}}Copy the code

store

Using Vuex in NUxT to manage state is very convenient by creating files in the Store/directory.

Each.js file in the store directory is converted to a submodule named in the state tree (index is the root module, of course).

In NUxT, there are two ways to use store.

One is the module mode, directly exporting state, getters, mutations and Actions as follows:

const state = (a)= > ({
    userInfo: null});const getters = {
  getUserInfo: state= > state.userInfo,
}

constmutations = { setUserInfo(state, data) { state.userInfo = data; }},const actions = {
  async requestUserInfo({ commit }) {
    const res = await axios.get('xx');
    commit('setUserInfo', res.data); }},export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
};
Copy the code

Store /index.js returns a method for creating an instance of vuex. store. Such as:

export default new Vuex.Store({
  state: (a)= > ({
    counter: 0
  }),,
  mutations: {
    ...mutations,
  },
  actions: {
    ...actions,
  },
  modules: {
    common,
  },
});
Copy the code

Pay attention to

Regardless of which mode you use, the value of your state should always be function to avoid returning a reference type, which can cause multiple instances to interact.

We can even split the module files into separate JS files, such as state.js, actions.js, retros.js and getters.js

Pay attention to

When using the split file module, you must remember to use the arrow function function. This is available lexically. The lexical scope this means that it always points to the owner of the function referencing the arrow. If the arrow function is not included, this will be undefined. The solution is to use the “normal” function, which points this to its own scope, so it can be used.

Build the deployment

Execute the build command and start

yarn run build
yarn run start
Copy the code

But to start this way, you have to keep the background on all the time, and I use the PM2 to start

Pm2 start

In package.json scripts configuration command:

{
  "scripts": {
    "pm2start": "cross-env NODE_ENV=production pm2 start ./server/pm2.config.json",}}Copy the code

Then, in the server/ directory, create the pm2.config.json file

{
  "apps": [{"name": "blog-front-ssr"."script": "./server/index.js"."instances": 0."watch": false."exec_mode": "cluster_mode"}}]Copy the code

The./server/index.js file is available when the project is created.

Finally, start:

npm run pm2start
Copy the code

conclusion

OK, so far, I have recorded all the problems I have encountered from completely using NUXT to the official launch of the website here. I hope it will also be helpful to you. Finally, this is my personal blog zhangjinpei.cn using Nuxt

Refer to the link

Vue SSR guide, Nuxt official website, TypeScript, Nuxt TypeScript, Restfule API Ali Cloud image processing