SSR Blog Project Built with Nuxt+Vue+Node

Keywords: MySQL Vue axios JSON Mobile

Previous blogs used Ghost, but they were attacked and blackmailed me for hundreds of dollars. By the way, data backup is important!I learned Vue.js a while ago. Node I saw before can remember a bit. It's mainly for exercise. This blog was written by myself without Hexo or Ghost.Due to the need of SEO, it is not interesting to write what you see yourself after all. The ultimate solution is: Vue.js+Nuxt.js+ES6+Webpack+Mysql+Noyde.js+Express.js.There are still a lot of things that I am not familiar with before I start, but I have finished them eventually. This project gives me a inspiration: practice is the fastest way to learn, so it is better to write a project by hand than to read more documents.

Vue.js and Nuxt.js

Vue.js

Currently popular front-end framework, official address: https://cn.vuejs.org/ , the document is well written, compliment me!

Nuxt.js

Nuxt.js Documents are also well written, say Vue.js series of documents are well written, once again praise, the official website is like introducing yourself:

Nuxt.js is a general application framework based on Vue.js.

By abstracting the client/server infrastructure, Nuxt.js focuses primarily on the UI rendering of the application.

Our goal is to create a flexible application framework on which you can initialize the infrastructure code for a new project or use Nuxt.js in an existing Node.js project.

Nuxt.js presets the various configurations needed to develop applications for server-side rendering using Vue.js.

In addition, we provide a command called nuxt generate, which provides the ability for Vue.js-based applications to generate corresponding static sites.

We believe the capabilities provided by this command are a new step towards the development of Web applications that integrate miscroservices.

As a framework, Nuxt.js provides many useful features for a typical client/server application architecture pattern, such as asynchronous data loading, middleware support, layout support, and so on.

To summarize, Nuxt.js is a framework that uses Vue, webpack and Node.js to help us implement SSR easily and easily.

About SSR

What is SSR

SSR is short for Server Side Render, which is server-side rendering.Before the absence of SPA, the vast majority of web pages were generated by server rendering: users send requests to the server, the server gets requests, then queries the database, dynamically generates a web page based on the queried data, and finally returns the content of the web page to the browser.

Now for Vue, in general, Vue.js runs in a browser, where you send a request, get the data returned in the background, and render the data into the desired HTML fragment using Vue.js.Now, we take the task of rendering components as HTML to Node, where we view Node as an invisible "browser" in which we render the components and send the rendered HTML directly to the actual browser (client), which is Vue SSR.

Benefits of SSR

SPA (Single-Page Application - Single-Page Application) built with Front End Framework (Vue, Angular,React) has a natural disadvantage that search engines cannot get content inside because content is acquired through Ajax.Right-click on the source code of an SPA web page and you will find that there is hardly anything in it.This is not acceptable for websites like blogs and news.

In summary, the benefits of SSR are the ability to SEO, by the way, because the content is already rendered on the server side and the number of requests can be reduced. For some older browsers (not supported by Vue.js), you can also see the basic content.

Development Summary

There are a lot of problems encountered in the development. Here I'll list some that can make you take as few detours as possible. The solution is as simple as I can say. Some things I will write a separate blog post.

Restful

The Restful API is used in the background of the project, and the express is used in the background framework. You can use Vue init nuxt-community/express-template <project-name>to generate a set of Express-based template files. The background code is in the server directory, which contains the contents about Node and express. It is not expanded here.

asyncData Multiple Requests

Refer to the following code:

async asyncData({ req, error }) {
    const page = 0
    let [pageRes, countRes] = await Promise.all([
        axios.get(`/api/post/page/${page}?scope=published`),
        axios.get('/api/post/count/published'),
    ])
    return {
        posts: pageRes.data.list,
        count: countRes.data.result,
    }
}

middleware

Middleware allows you to define a custom function that runs before a page or set of pages is rendered.Each middleware should be placed in the middleware/directory.The name of the file name will become the name of the middleware (middleware/auth.js will become auth middleware).Here is an example:

import { isLogin } from '../util/assist'
const needAuth = require('../util/api.config').needAuth
export default function ({ isClient, isServer, route, req, res, redirect }) {
    //On the server side, judge if login is required (if address is entered directly, it cannot be determined on the client side)
    if (isServer) {
        let cookies = req.cookies
        let path = req.originalUrl;

        if (path.indexOf('admin') > 0 && !cookies.token) {
            redirect('/login')
        }
    }
    //Interpret whether login is required on the client side
    if (isClient) {
        if (route.path.indexOf('admin') > 0 && !isLogin()) {
            redirect('login')
        }
    }
}

Node Loop+Asynchronous Problem

There is a requirement in the project to display several tags (Tags) corresponding to a Post. The solution is to get a PostList, then loop through the List and get a PostId to look up the corresponding Tag according to the PostId.Since fetching the PostList asynchronous operation and then putting a lot of asynchronous operations inside the loop (fetching the Tag), you can't write it down by callback. Finally, you use the async library and paste a piece of code:

//Get Post List
let list = (params, callback) => {
    postModel.list(params, (err, posts) => {
        if (err) {
            return callback({ code: 404, message: 'no result' });
        }
        //get each posts' tags
        async.eachSeries(posts, (post, tagCallback) => {
            postTagModel.tagsByPostId(post.id, (err, result) => {
                if (err) {
                    tagCallback(err)
                }
                post.tags = result;
                tagCallback()
            });
        }, (err) => {
            if (err) {
                callback({ code: 404, message: 'no result' });
                return false;
            }
            callback({ code: 404, message: 'no result', list: posts });
        });
    });
}

Static Resources

In Nuxt, you can place the static file in the static folder in the project root directory, and then use the root path / directly to access it.

<!--Assuming there is a picture in the static directory, my-image.png, you can access it directly like this-->
<img src="/my-image.png"/>

Land

The first time I used restful, the landing problem kept bothering me, I searched a lot of data, and the final solution was to use token.

At the front end, it is detected that the user jumps to the landing page without logging in. The user sends a landing request, verifies the user name and password in the background, and returns a token after successful verification.The front end receives the token and stores it locally. It then takes it with it each time it sends a request. The subsequent requests verify the token in the background and consider the login successful if it is legal.

The code below is used to generate token (using jwt-simple), uid is the user ID, exp is seven days later, and jwtSecret is the key for encryption and decryption.

let auth = (user, callback) => {
    if (user.account.trim() == '') {
        return callback({ code: 403, message: 'Incorrect username' });
    }
    if (user.password.trim() == '') {
        return callback({ code: 403, message: 'Incorrect password' });
    }
    userModel.auth(user, (err, user) => {
        if (err) {
            return callback({ code: 404, message: 'Logon Failure' });
        }
        if (user.length === 1) {
            //Set seven-day validity period
            let expires = moment().add(7, 'days').valueOf();

            let token = jwt.encode({
                uid: user[0].id,
                exp: expires
            }, jwtSecret)

            return callback({ code: 200, message: 'success', token: token });
        }
        callback({ code: 404, message: 'Logon Failure' });
    });
}

The Ajax tool I use is axios, and the code below adds a header to all requests.

// Intercept request
$http.interceptors.request.use(
  config => {
    if (typeof document === 'object') {
      let token = getCookieInClient('token')
      if (token) {
        config.headers.Authorization = token;
      }
    }
    return config;
  }, err => {
    return Promise.reject(err);
  }
);

Here's the middleware for checking token. First, you can decide if the interface needs to be authenticated. Next is not necessary. If you need to, you can get and verify token. The rough way to do this is to directly determine whether token is valid or not. Of course, there is a safer way to do this. You can search for it yourself.The sign of success is that token checks are valid and proceeds to the next step.If the check fails, it returns directly, and the front end jumps to the landing page based on the response.

module.exports = function (req, res, next) {
    let path = req.originalUrl
    
    //Interface does not require login: direct next
    if (needAuth.indexOf(path) < 0) {
        return next();
    }
    
    //Interface requires login
    var token = req.headers['authorization']
    if (!token) {
        return res.json({
            code: 401,
            message: 'you need login:there is no token'
        })
    }
    
    try {
        //Decrypt acquired token
        let decoded = jwt.decode(token, jwtSecret);

        //Validity Period
        if (decoded.exp <= Date.now()) {
            return res.json({
                code: 401,
                message: 'you need login:token is expired'
            });
        }
        next();
    } catch (err) {
        return res.json({
            code: 401,
            message: 'you need login:decode token fail'
        })
    }
};

nuxt.config

Configuration file for nuxt, specific configuration items can be referenced This link , here is my configuration file, which should be understood at a glance.

module.exports = {
  //head label of page
  head: {
    title: 'JustYeh Front End Blog',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1, user-scalable=no' },
      { hid: 'description', name: 'description', content: 'Ye Wenxiang's Front End Blog' },
      { name: 'renderer', content: 'webkit' },
      { 'http-equiv': 'X-UA-Compatible', content: 'IE=edge' },
      { name: 'author', content: 'Ye Wenxiang,justyeh@163.com' },
      { name: 'apple-mobile-web-app-title', content: 'Ye Wenxiang's Front End Blog' },
      { name: 'apple-mobile-web-app-capable', content: 'yes' },
      { name: 'apple-mobile-web-app-status-bar-style', content: '#263238' },
      { name: 'screen-orientation', content: 'portrait' },
      { name: 'x5-orientation', content: 'portrait' },
      { name: 'full-screen', content: 'yes' },
      { name: 'x5-fullscreen', content: 'true' },
      { name: 'browsermode', content: 'application' },
      { name: 'x5-page-mode', content: 'app' },
      { name: 'theme-color', content: '#263238' },
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  //Global Referenced css Files
  css: ['~assets/css/main.css', '~assets/css/font-awesome.min.css'],
  // loading effect at top of page
  loading: {
    color: '#04acf7',
    height: '4px',
    failedColor: 'red'
  },
  //Transition effect of page
  transition: {
    name: 'page'
  },
  //Configure Routing Middleware
  router: {
    middleware: 'adminAuth'
  }

}

deploy

Deployment has been a little difficult, and this time we did encounter a lot of problems on it, let alone say.Ultimately used is pm2 Now assume that you have installed packages such as node, pm2, vue, and so on, run the following commands in turn:

#Enter the directory where the file is located
cd your_project
#Dependencies required to install the project
npm insatll
#Pack
npm run build
#Run, --name'your project name'is optional
pm2 start npm [--name 'your project name'] -- start

Say at the end

It's trivial, and it might help if you happen to have problems with it.I don't know how to unfold a systematic comb, I can only apologize.

Okay, here's all the code: https://github.com/justyeh/justyeh.com This is the final result: http://justyeh.com/ Since this is a private project, it won't be published online or in the background, but you can clone my project directly, have a database of everything, and run directly locally.

Level is limited, if there are any errors, please include.You can give a star if you like.If you have any good suggestions and comments, you are welcome Contact me!

Posted by jgh84 on Fri, 14 Jun 2019 10:34:20 -0700