vue seo优化之nuxt脱坑指南

前言

公司的项目需要做seo,经过翻阅nuxt文档 、相关资料及自己实战后,特意整理分享这篇文章,方便入坑的同学脱坑,同时自己也做下笔记,如有说错的地方,还望指点。

没有了解过seo和ssr服务器渲染的同学,建议先把基础知识给补一下

vue ssr指南 ssr相关知识

站长工具 查相关网站的seo 收录

nuxt中文网 nuxt中文网

一、nuxt和vue有什么区别?

1.服务器端渲染和客户端渲染
Nuxt: ssr(服务器端渲染),正如字面上的意思,需要把自己编写的代码放在服务器上跑,对服务器性能有一定的要求,需要node环境配合pm2 nginx部署;

vue: spa单页应用程序(客户端渲染),npm run build 成功后即可把dist目录的静态文件放在服务器部署;

2.应用框架
Vue多数用于开发spa单页应用程序,不利于搜索引擎的seo操作;对ssr服务器端渲染的支持并不是很友好,听说在vue3会对这方面有很大的提高,这也让我很期待;

而nuxt简单来说,就是在vue的基础上加上了服务器端渲染的能力,再扩展自己的特点,如生命周期 路由 es2015+语法支持等

二、nuxt项目搭建和配置

1.项目搭建
为了快速入门,Nuxt.js团队创建了脚手架工具 create-nuxt-app

npn create-nuxt-app <项目名>

详细流程可以参考nuxt官网,在此就不累赘说明了

2.nuxt.config.js配置文件

module.exports = {
  globalName: "hq",
  mode: "universal",
  server: {
    port: 3000,
    // host: "127.0.0.1",
   //host: '0.0.0.0', //服务器外网访问
    host: "localhost"
  },
  // buildDir: 'nuxt-dist', // Build 发布目录
  /*
   ** Headers of the page
   */
  head: {
    title: "",
    meta: [
      { charset: "utf-8" },
      /*优先使用 IE 最新版本和 Chrome*/
      { "http-equiv": "X-UA-Compatible", content: "IE=edge,chrome=1" },
      { name: "format-detection", content: "telephone=no" },
      { hid: "renderer", name: "renderer", content: `webkit` },
      {
        hid: "viewport",
        name: "viewport",
        content:
          "width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0,user-scalable=no"
      },
      {
        hid: "keywords",
        name: "keywords",
        content: ` `
      },
      {
        hid: "description",
        name: "description",
        content:
          ""
      }
    ],
    // 使用外部资源文件,自动生成 script 标签
    script: [],
    link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }]
  },
  /*
   ** Customize the progress-bar color
   */
  loading: { color: "#fa6e32", height: "2px" },//进行ajax请求时顶部进度条的颜色
  render: {
    resourceHints: false
  },

  /*
   ** Global CSS
   */
  css: [
    { src: "ant-design-vue/dist/antd.less", lang: "less" },
    "~/assets/css/common.css",
    "~/assets/css/hover.css",
    "~/assets/iconfont/iconfont.css",
    { src: "~/assets/scss/public.scss", lang: "scss" },
    { src: "~/assets/less/theme.less", lang: "less" }
  ],
  /*
   ** 引入插件
   ** ssr: false 表示只在客户端生效
   */
  plugins: [
    "@/plugins/antd-ui",
    "@/plugins/router",
    "@/plugins/axios.js",
    { src: "@/plugins/polyfill", ssr: false },
    { src: "@/plugins/vueQr.js", ssr: false },
    { src: "@/plugins/vue-lazyload.js", ssr: false  },
    { src: "@/plugins/swiper.js", ssr: false }
  ],
  // 配置路由中间件
  router: {
    middleware: ["i18n"]
    // base:"/en/"
    // base:"/"
  },
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [
    // Doc: https://github.com/nuxt-community/eslint-module
  ],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // nuxt 模块扩展
    // Doc: https://axios.nuxtjs.org/usage
    "@nuxtjs/axios",
    "@nuxtjs/style-resources",
    ["cookie-universal-nuxt", { parseJSON: false }]
  ],
  styleResources: {
    //预处理器配置 一般用于加载全局变量之类
    // style
    stylus: "./assets/css/css.styl",
    scss: ["./assets/scss/public.scss"]
  },
  /*
   ** Axios module configuration
   ** See https://axios.nuxtjs.org/options
   */
  axios: {
    // 是否允许跨域
    proxy: true
  },
  proxy: {},
  /*
   ** Build configuration
   */
  build: {
    vendor: ["axios"], // 为防止重复打包
    transpile: [/^ant-design-vue/],
    //提取css到单独link文件
    extractCSS: {
      allChunks: true
    },
    loaders: {
      less: {
        javascriptEnabled: true,
        /**
         * ant-design-vue 修改样式变量
         */
        modifyVars: {
          "primary-color": "#fa6e32" // 全局主色
        }
      }
    },
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) {}
  }
};

3.axios配置

import { notification, Modal } from "ant-design-vue";
import configSettings from "@/config/defaultSettings";
import { getToken} from "@/utils/fn";
import { isBrowser } from "~/environment";
const ERRORS = new Map([
  [500, "服务器异常..."],
  [404, "未找到对应资源"],
  [401, "未鉴权"]
]);

export default function({ $axios, redirect, store }) {
  // 基本配置
  // $axios.defaults.baseURL = 'https://xxxx'; //正式
  $axios.defaults.baseURL = "http://xxxx:8070"; //测试
  $axios.defaults.timeout = 20000;

  // 请求回调
  $axios.onRequest(config => {
    const token = getToken();
//设置请求头公共toeken
    if (token) {
      config.headers.common[configSettings.token] = token;
    }
    return config;
  });

  // 返回回调
  $axios.onResponse(response => {

    return res;
  });

  // 错误回调
  $axios.onError(error => {
    return Promise.reject(error.response);
  });
}

4.默认HTML文件
在根目录下新建app.html

<!DOCTYPE html>
<!--[if IE 9]>
<html lang="en-US" class="lt-ie9 ie9" {{ HTML_ATTRS }}><![endif]-->
<!--[if (gt IE 9)|!(IE)]><!-->
<html {{ HTML_ATTRS }}>
  <!--<![endif]-->
  <head {{ HEAD_ATTRS }}>
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Cache-Control" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
    {{ HEAD }}
  </head>
  <body {{ BODY_ATTRS }}>
    {{ APP }}
  </body>
   <!--这里也可以引入script标签-->
</html>

app.html跟vue脚手架生成后的public目录里面的index.html是一样的功能,只不过nuxt的脚手架没有默认生成该文件,注意:这里的{{HEAD}} 、{{APP}}是区别大小写的,不要去动他;

5.配置error页面
在根目录找到layouts并创建error.vue文件

<template>
  <div class="exception center">
    <div class="imgBlock">
      <div class="imgEle" :style="{backgroundImage: `url(${type[error.statusCode].img})`}">
      </div>
    </div>
    <div class="content">
      <h1>{{ type[error.statusCode].title }}</h1>
      <div class="desc">{{ type[error.statusCode].desc }}</div>
      <div class="actions">
        <nuxt-link to="/" >
          <a-button type="primary" >返回首页</a-button>
        </nuxt-link>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Error',
  props: ['error'],
  computed: {},
  data() {
    return {
      type: {
        403: {
          img:
            "https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg",
          title: "403",
          desc: "抱歉,你无权访问该页面",
        },
        404: {
          img:
            "https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg",
          title: "404",
          desc: "抱歉,你访问的页面不存在或仍在开发中",
        },
        500: {
          img:
            "https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg",
          title: "500",
          desc: "抱歉,服务器出错了",
        },
      },
    };
  },
  mounted() {
  },
  methods: {}
}
</script>
<style lang="less">
  @import "~ant-design-vue/lib/style/index";

  .exception {
    display: flex;
    align-items: center;
    height: 80%;
    min-height: 500px;

    .imgBlock {
      flex: 0 0 56.5%;
      width: 56.5%;
      padding-right: 152px;
      zoom: 1;
      &::before,
      &::after {
        content: ' ';
        display: table;
      }
      &::after {
        clear: both;
        height: 0;
        font-size: 0;
        visibility: hidden;
      }
    }

    .imgEle {
      float: right;
      width: 100%;
      max-width: 430px;
      height: 360px;
      background-repeat: no-repeat;
      background-position: 50% 50%;
      background-size: contain;
    }

    .content {
      flex: auto;

      h1 {
        margin-bottom: 24px;
        color: #434e59;
        font-weight: 600;
        font-size: 72px;
        line-height: 72px;
      }

      .desc {
        margin-bottom: 16px;
        color: @text-color-secondary;
        font-size: 20px;
        line-height: 28px;
      }

      .actions {
        button:not(:last-child) {
          margin-right: 8px;
        }
      }
    }
  }

  @media screen and (max-width: @screen-xl) {
    .exception {
      .imgBlock {
        padding-right: 88px;
      }
    }
  }

  @media screen and (max-width: @screen-sm) {
    .exception {
      display: block;
      text-align: center;
      .imgBlock {
        margin: 0 auto 24px;
        padding-right: 0;
      }
    }
  }

  @media screen and (max-width: @screen-xs) {
    .exception {
      .imgBlock {
        margin-bottom: -24px;
        overflow: hidden;
      }
    }
  }
</style>

三、nuxt生命周期

Nuxt.js 是一个基于 Vue.js 的通用应用框架,支持vue生命周期的同时,扩展了服务端的钩子,特别是asyncData,使得你能够在渲染组件之前异步获取数据。

export default {
  middleware () {}, //服务端
  validate () {}, // 服务端
  asyncData () {}, //服务端 nuxtjs进行服务器渲染关键钩子,只支持page目录下的文件,不支持components目录下的组件
  fetch () {}, // store数据加载
  beforeCreate () {  // 服务端和客户端都会执行},
  created () { // 服务端和客户端都会执行 },
  beforeMount () {}, 
  mounted () {} // 客户端执行
}

四、页面模板

export default {
  components: { }, 
  head() {
    return {
      title: this.college.title,
      meta: [
        {
          hid: 'description',
          name: 'description',
          content: this.college.title
        },
        {
          hid    : 'keywords',
          name   : 'keywords',
          content: this.college.title
        }
      ]
    }
  },

  watchQuery: ['collegeId'],//路由上该参数发生改变时,asyncData fetch 会重新执行
  async asyncData({app}) {
    const query = app.context.query;
    let [college,'回调1','回调2'] = await Promise.all([
      app.$axios.get('/web/api/college', {
        params: {
          collegeId: query.collegeId,
        }
      }),
      //可多个请求
    ]);
    return {
      college:college,
    }
  },
  data() {
    return {};
  },
  created() {},
  watch: {},
  mounted() {
  },
};

五、项目部署

1.确保本地代码无误后,运行命令打包,成功后会在根目录出现 .nuxt的目录;

npm run build

2.除了node_modules和其他没必要的文件夹以外,以下文件上传到服务器新建好的目录下

nuxt.png

3.安装nodejs

4.在上传的根目录里cmd运行命令安装依赖包

npm install -production

5.运行npm start,出现http://localhost:3000 证明成功,在服务器访问预览正常后即可关闭服务

npm start

6.安装pm2,没有了解过这个的东西可以先百度下 了解更多pm2

npm i pm2 -g

7.设置pm2开机自启
安装并配置pm2-windows-service

npm i -g pm2-windows-service 

添加系统环境变量(右键 [我的电脑] - [属性] - [高级系统设置] - [环境变量] - 新建 [系统变量] )

PM2_HOME=C:\Users\Administrator.pm2 //路径默认在当前用户下的.pm2

以管理员权限打开新的cmd命令行窗口,执行以下命令来安装服务

pm2-service-install 

提示Perform environment setup ? 选 n, 继续,此时, PM2服务已安装成功并已启动, 可以通过 [win + r] - [services.msc]
来查看,服务名称为PM2

8.使用pm2在根目录启动nuxt项目,很多人因为命令报错会卡在这里,这里就相当于第五点的npm start ,
启动成功后,再运行pm2 save,

pm2 start npm --name "xxxx-nuxt" -- run start //启动项目nodejs服务

pm2 save //保存当前pm2 正在启动的NodeJS服务

9.配置Nginx
完成以上操作后,基本上已经结束了,接下来需要用Nginx配置下项目,没有安装的同学可自行百度;

worker_processes  1;

    events {
        worker_connections  1024;
    }

    http {
        include       mime.types;
        default_type  application/octet-stream;
        sendfile        on;
        keepalive_timeout  65;

        upstream nodenuxt {
            server 127.0.0.1:3000; # nuxt 项目启动后地址
            keepalive 64;
        }
        server {
            listen       80;
            server_name  www.baidu.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;
            }
        }
    }

修改配置文件后再重新启动Nginx,如果出现报错,应该是服务器的80端口被占用了,可修改配置文件的listen 端口,也可以查看是哪个服务占用再去禁用该服务,看个人项目需求

start nginx //启动nginx
nginx -s reload //重新载入nginx(当配置信息发生修改时)

nginx -s quit //停止nginx

nginx -h 查看帮助信息

nginx 启动成功后,就可以通过域名访问项目了,大功告成!

总结

本文并非原创,欢迎转载,记录下学习nuxt的过程,也可以让入门的同学少走弯路,后面有时间我会整理相关nuxt的代码发布一套demo出来给大家参考,希望能帮助到大家,拜拜!

——By
kkc_hq

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