nuxt.js实现服务端渲染ssr(环境配置、 多环境开发、进程守护、服务端镜像)

nuxt.js是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用——nuxt官网

images.png

根据自己在这上方面一条路走到黑,摸滚带爬的踩坑经验,接下来我将从以下几个方面给各位接触到或者未来接触到这方面知识的码友进行全方位的讲解和剖析。

  1. 背景
  2. nuxt框架的概述
  3. 百度蜘蛛爬虫的机制
  4. nuxt初探(开发环境搭建)
  5. 深入nuxt ssr(服务端渲染)
  6. 多环境配置 (开发、测试、生产)
  7. Nginx反向代理
  8. pm2守护node进程配置
  9. 服务端docker容器部署前端工程
  10. 总结
背景

最近因公司业务需求,需要对商城官网做seo优化。因为第一版前端用的是纯vue写的,经Vue-cli集成的webpack打包还有代码压缩处理后生成一串js的静态文件。因百度蜘蛛无法对纯js的网页进行爬取收录。所以不利于网站的排名。紧接我们和领导开会拍板使用服务端渲染(以下用ssr指代),nuxt刚好是vue团队打造的基于vue的ssr的框架,简单易于上手,避免了前端攻城狮们自己用node.js搭建服务。但初始我们采用的是nuxt的静态化部署,nuxt generate ,这样可以解决一部分需求,但每次更新内容后都要前端手动执行命令打包。而且通过百度蜘蛛爬取趋势图显示这种方式并不理想。最后,我们还是在原来的基础上采用nuxt的第二种方式前端做ssr处理,上线后经百度蜘蛛爬取趋势图显示有很大程度的提高,利于seo,且官网被百度收录的页面也逐渐增加。由此告一段落。

nuxt框架的概述

nuxt作为一个框架,则集成了vue2、vue-route、vuex、vue ssr、vue-meta等组件/框架,其是用webpack、和vue-loader、babel-loader来处理代码的自动化构建工作(打包、代码分层、压缩等)
nuxt框架提供了两种部署方式:
1. 静态化部署(预渲染)-- 通过 nuxt generate 命令实现。该命令依据应用的路由配置将每一个路由静态化成为对应的 HTML 文件
2. ssr部署 -- 先通过nuxt build编译构建再通过nuxt start开启一个web服务
在服务端调取接口时,主要是用到了asyncData/fetch方法。使得我们可以在设置组件的数据之前能异步获取或处理数据。

asyncData方法会在组件(限于页面组件)每次加载之前被调用。它可以在服务端或路由更新之前被调用。 在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用 asyncData方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。

注意:由于asyncData方法是在组件 初始化 前被调用的,所以在方法内是没有办法通过 this 来引用组件的实例对象。

fetch 方法用于在渲染页面前填充应用的状态树(store)数据, 与 asyncData 方法类似,不同的是它不会设置组件的数据。
类型: Function
如果页面组件设置了 fetch 方法,它会在组件每次加载前被调用(在服务端或切换至目标路由之前)。fetch 方法的第一个参数是页面组件的上下文对象 context,我们可以用 fetch 方法来获取数据填充应用的状态树。为了让获取过程可以异步,你需要返回一个 Promise,Nuxt.js 会等这个 promise 完成后再渲染组件。

警告: 您无法在内部使用this获取组件实例,fetch是在组件初始化之前被调用

大概了解了这些知识就可以做ssr工作了,路由 环境配置等可到中文官网https://zh.nuxtjs.org 查阅

百度蜘蛛爬虫的机制

百度蜘蛛是百度搜索引擎的一个自动化程序,它会不断的访问收集互联网上的网页、文章、视频等,通过抓取链接来收录网站,计算网站的权重和排名。纯html等静态化网站对百度蜘蛛比较友好,且百度蜘蛛几乎不会爬取js动态的网站,如vue/react构建的且经webpack/gulp等构建工具压缩处理过的网站。百度蜘蛛爬取网站是从主站开始爬,一次根据网站暴露的内链依次往深层次爬取。meta的设置,以及网站TDK的优化,网站结构优化,外链,文章原创等同样对SEO有很大作用,但本文主要是从技术层面入手,则主要是针对网站内链的处理以及基于vue等现在技术流做ssr处理。

nuxt初探(开发环境搭建)
安装

我这里采用nuxt.js团队创建的脚手架creat-nuxt-app搭建。
注: 开发必备环境 npx (npm v5.2.0+) node (v4.0+)
我的开发环境npm v5.5.1 node v8.9.1 win10
在终端输入以下命令

npx create-nuxt-app nuxt-demo

然后你会看到

nuxt-1.png

nuxt-2.png

当执行命令

npm run dev

当你看到如下图恭喜你成功搭建起项目


nuxt-3.png

当你和后端交互时,为了解决跨域问题这边需要通过proxy利用本地node服务器做个反向代理
在nuxt.config.js中配置

module.exports= {
    modules: [
        '@nuxtjs/axios'
    ],
    axios: {
        proxy: true, //开启代理
        credentials: true, //跨域请求需使用凭证
    },
    proxy: [
        ['/api',{ 
            target: 'http://example.com/api', // (后端请求地址)
            changeOrigin: true,
            pathRewrite: {'^/api': ''}
        }]
    ]
}

附上刚搭建完的nuxt.config.js和package.json

// nuxt.config.js

module.exports = {
  mode: 'universal',
  /*
  ** Headers of the page
  */
  head: {
    title: process.env.npm_package_name || '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  /*
  ** Customize the progress-bar color
  */
  loading: { color: '#fff' },
  /*
  ** Global CSS
  */
  css: [
    'element-ui/lib/theme-chalk/index.css'
  ],
  /*
  ** Plugins to load before mounting the App
  */
  plugins: [
    '@/plugins/element-ui'
  ],
  /*
  ** Nuxt.js dev-modules
  */
  buildModules: [
    // Doc: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module',
  ],
  /*
  ** Nuxt.js modules
  */
  modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    '@nuxtjs/pwa',
    // Doc: https://github.com/nuxt-community/dotenv-module
    '@nuxtjs/dotenv',
  ],
  /*
  ** Axios module configuration
  ** See https://axios.nuxtjs.org/options
  */
  axios: {
  },
  /*
  ** Build configuration
  */
  build: {
    transpile: [/^element-ui/],
    /*
    ** You can extend webpack config here
    */
    extend (config, ctx) {
    }
  }
}

//package.json
{
  "name": "nuxt-demo",
  "version": "1.0.0",
  "description": "for a nuxt demo about ssr",
  "author": "kevin xie",
  "private": true,
  "scripts": {
    "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
    "build": "nuxt build",
    "start": "cross-env NODE_ENV=production node server/index.js",
    "generate": "nuxt generate",
    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
    "test": "jest"
  },
  "dependencies": {
    "nuxt": "^2.0.0",
    "cross-env": "^5.2.0",
    "express": "^4.16.4",
    "element-ui": "^2.4.11",
    "@nuxtjs/axios": "^5.3.6",
    "@nuxtjs/pwa": "^3.0.0-0",
    "@nuxtjs/dotenv": "^1.4.0"
  },
  "devDependencies": {
    "nodemon": "^1.18.9",
    "@nuxtjs/eslint-config": "^2.0.0",
    "@nuxtjs/eslint-module": "^1.0.0",
    "babel-eslint": "^10.0.1",
    "eslint": "^6.1.0",
    "eslint-plugin-nuxt": ">=0.4.2",
    "@vue/test-utils": "^1.0.0-beta.27",
    "babel-jest": "^24.1.0",
    "jest": "^24.1.0",
    "vue-jest": "^4.0.0-0"
  }
}

深入nuxt ssr(服务端渲染)

如果你不需要做ssr处理的话直接执行nuxt generate则nuxt会为你生成静态化页面 ,然后再执行nuxt build 打包后将dist文件夹推上服务器就可以上线了,但这种seo不太友好但比之前vue构建好一点。
但要做ssr处理的话就需换种方式处理也就是nuxt提供的第二种方式,在服务器搭个node服务器然后直接在上面跑。这样的话,配置需要改,且与后台的请求也需要改,改为nuxt给我们提供asyncData/fetch。因为nuxt也是基于vue开发的,所以生命周期也一样,但nuxt在服务端中会触发beforeCreate 、created两个生命周期。其次,nuxt有自己一套服务器渲染流程。


nuxt-4.PNG

Nuxt.js 提供了几种不同的方法来使用 asyncData 方法,你可以选择自己熟悉的一种来用:

  1. 返回一个 Promise, nuxt.js会等待该Promise被解析之后才会设置组件的数据,从而渲染组件.
  2. 使用 async 或 await
  3. 使用回调函数
返回Promise
export default {
  asyncData ({ params }) {
    return axios.get(`https://my-api/posts/${params.id}`)
      .then((res) => {
        return { title: res.data.title }
      })
  }
}
使用async或await
export default {
  async asyncData ({ params }) {
    const { data } = await axios.get(`https://my-api/posts/${params.id}`)
    return { title: data.title }
  }
}
使用回调函数
export default {
  asyncData ({ params }, callback) {
    axios.get(`https://my-api/posts/${params.id}`)
      .then((res) => {
        callback(null, { title: res.data.title })
      })
  }
}

另外,还要注意一下在做登录处理等需要获取cookie或者服务端存储的session时,需要使用到vuex状态树,在状态树中有个方法非常有用 nuxtServerInit

actions: {
  nuxtServerInit ({ commit }, { req }) {
    if (req.session.user) {
      commit('user', req.session.user)
    }
  }
}
多环境配置 (开发、测试、生产)

因为在一个工程项目中我们都是有开发、测试、生产这样的验收流程的。而往往我们测试和生产所连接的数据库都是不同的,且请求域也不一样。因此为了开发方便我们就很需要多环境的配置。在spa中我们可以在package.json中通过cross-env这个node环境变量设置,但在ssr中这个在服务端请求时不生效,这里我当时找了很久google之类最后还是通过@nuxtjs/dotenv解决了。还有特别注意到@nuxtjs/axios中有提供环境变量API_URL来复写baseURL和API_URL_BROWSER来复写browserBaseURL,这在多环境配置中很有用。
你可以在nuxt.config.js中引入并且在module中使用。

require('dotenv').config({path: '.env'})
module.exports={
    modules: [
        '@nuxtjs/axios',
        '@nuxtjs/dotenv'
    ]
}

同时你可以在根目录下配置相关环境配置文件。

.env.dev

NODE_ENV=development
API_URL_BROWSER=http://localhost:3000
API_URL=http://localhost:3000/api

.env.test

NODE_ENV=test
API_URL_BROWSER=http://test.example.com
API_URL=http://test.example.com/api

.env.prod

NODE_ENV=production
API_URL_BROWSER=https://www.example.com
API_URL=https://www.example.com/api

当你用axios做跨域请求时,你就可以自动根据环境给axios配置baseURL。

import axios from 'axios';
const axiosInstance = axios.create({
    timeout: 10000,
    baseURL: process.env.API_URL
})

在某些需要跳转的链接nuxt-link或a链接中你可以声明一个变量对域名做统一处理。

export default {
    data () {
        return {
            BASE_PATH: process.env.API_URL_BROWSER
        }
    }
}
Nginx反向代理

在生成环境中,我需要使用到nginx做代理服务器,解决跨域。因为我们的项目的前后端可能部署在不同的服务上。

upstream nodenuxt {
    server 127.0.0.1:3000; #nuxt项目 监听端口
    keepalive 64;
}
server {
    listen 80;
    server_name https://www.example.com; #访问域名  
    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://nodenuxt; #反向代理
    }
}
pm2守护node进程配置

当我们配置以上内容后在服务端执行npm start项目可以跑起来,但我们的项目总会挂掉的,为了使我们服务进程常驻,我们要用到pm2来守护node进程。并且可以用它来做自动重启、性能监控以及负载均衡。
你可以全局安装npm install pm2 -g
其他麻油说 执行命令可以成功,但我这边还是没成功pm2 start npm --name "nuxt-demo" -- run start //(nuxt-demo为你的package.json中的项目名)
我们知道package.json这个文件,当我们执行npm run dev的时候,其实使用npm去启动了./node_modules/nuxt/bin/nuxt这个文件。当我们cd到我们的项目目录之后,我们最终可以执行如下命令来启动:

pm2 start ./node_modules/nuxt/bin/nuxt.js -- start

这样可以将项目跑起来,但当项目报错时我们无法看到日志,因此我这里在根目录通过配置pm2.config.js。

module.exports = {
    apps: [
        {
            name: 'nuxt-demo',//项目名称
            cwd: './',//当前工作路径
            #script: 'npm',//实际启动脚本
                          script: './node_modules/nuxt/bin/nuxt.js ',//或者直接执行这个脚本
            args: 'run start',//参数
            autorestart: true, //自动重启
            error_file: 'logs/nuxt-demo-err.log',//错误日志
            out_file: 'logs/nuxt-demo-out.log', //正常运行日志
            exec_mode: 'cluster',// 应用启动模式,支持fork和cluster模式
            min_uptime: '60s', //应用运行少于时间被认为是异常启动
            restart_delay: '60s',//重启时延
            instances: 4,//开启4个实例,仅在cluster模式有效,用于负载均衡
            watch: true,//监控变化的目录,一旦变化,自动重启
            watch: ['.nuxt', 'nuxt.config.js'],//监控变化的目录
            watch_delay: 1000,//监控时延
            ignore_watch: ['node_modules'],//从监控目录中排除
            watch_options: { // 监听配置
                'followSymlinks': false,
                'usePolling': true
            }
        }
    ]
}

这里我是在package.json中引用这个配置文件的。在服务器中执行命令就行。(若有报错可能需要安装babel转译 ```npm install babel-cli babel-core babel-preset-es2015 --save-dev)

{
    "scripts": {
        "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server --exec babel-node",
        "build": "nuxt build",
        "start:test": "cross-env NODE_ENV=test node server/index.js pm2 start pm2.config.js --exec babel-node",
        "start:prod": "cross-env NODE_ENV=production node server/index.js pm2 start pm2.config.js --exec babel-node",
    }
}
服务端docker容器部署前端工程

建立Dockerfile 参考这篇文章

FROM node:alpine

RUN mkdir -p /app/src
COPY ./src /app/src
WORKDIR /app/src

ENV HOST "0.0.0.0"

RUN sed -i "s/dl-cdn.alpinelinux.org/${ALPINE_REPOSITORIES}/g" /etc/apk/repositories

RUN apk add --no-cache make gcc g++ python

RUN npm config set registry https://registry.npm.taobao.org
RUN npm install -g pm2
RUN npm install
RUN npm run build
RUN npm cache clean --force

RUN apk del make gcc g++ python

EXPOSE 3000
CMD ["npm", "run", "start:test"]
#CMD ["npm", "run", "start:prod"]

然后构建镜像

docker build -t nuxt-demo

启动容器

docker run -dt -p 3000:3000 nuxt-demo

注意:这里服务端部署后需要0.0.0.0:3000访问,则我们前端还需配置主机号和端口,我这边在package.json中没成功,在nuxt.config.js中配置成功了。

module.exports = {
    server: {
        host: '0.0.0.0',
        port: 3000
    }
}
总结

总得来说,在coding world中要不断的学习,没什么问题解决不了,坚持就对了。若有小伙伴对以上内容有不解欢迎评论,我会尽力为你答疑解惑的。最后喜欢这篇文章的小伙伴关注一下小生并点个赞。
附:

史记·大疫

己亥末,庚子春,荆楚大疫,染者数万,众惶恐,举国防,皆闭户,道无车舟,万巷空寂。然外狼亦动,垂涎而候,华夏腹背芒刺。幸龙魂不死,风雨而立。医无私,警无畏,民齐心!政者,医者,兵者,扛鼎逆行勇战矣!商客,名家,百姓,仁义者,邻邦献物捐资,叹山川异域,风月同天,岂曰无衣,与子同裳!能者竭力,万民同心。月余,疫除,终胜。此后百年,风调雨顺,国泰民安!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容