Eson Wong  /  文章

Nuxt.JS 介绍

技术Web 开发约 4 分钟
Nuxt.js logo

Nuxt.js 是一个基于 Vue 可用来创建服务端渲染(SSR) Web 应用的框架。

为什么要服务器端渲染?

Web 前端的服务器端渲染(SSR)主要有以下好处:

  1. 更好的 SEO,搜索引擎可以爬取完全渲染的  HTML
  2. 更快的内容到达时间(减少访问的白屏时间)

相比非 SSR 的 Web 应用它也会带来一些缺点:

  1. 消耗服务器资源
  2. 对前端开发人员能力要求更高

Nuxt.js SSR 的流程

要做到上述优点还能保证用户交互体验,要做到在首次加载页面的时候才进行 SSR。下面的 Nuxt.js 的简要流程图反应的何时才会进行 SSR。

在浏览器进行站内导航时,无需 SSR。

Nuxt.js flow

完整的生命周期:https://nuxtjs.org/docs/concepts/nuxt-lifecycle/

开启服务器端渲染

Nuxt.js 也可以以单页应用模式和静态站点模式运行,默认以 SSR 模式运行,可在项目根目录下的配置文件 nuxt.config.js 中做修改

// nuxt.config.js

export default {
  ssr: true, // default value
};

路由 和 Pages 文件夹

Nuxt.js 框架会 根据 pages 文件夹的结构自动生成路由的规则:

pages/
--| users.vue       /users 路径下的页面的父组件,子组件渲染的地方使用 <NuxtChild/>
  | users/
-----| index.vue    访问路径: /users
     | search.vue   访问路径: /users/search
_.vue 访问路径: 未匹配的路径

_ 开头命名 vue 文件以定义 params, 可用在组件中以 this.$route.params 取得:

pages/
--| users.vue
--| users/
-----| _id.vue    访问路径: /users/123 获取参数 this.$route.params.id

⚠️ 使用 NuxtLink 组件在站内路由导航。

⚠ pages 文件夹不要写非页面组件,会生成不必要的路由配置。非页组件应写在 components 文件夹内。

问答: pages/user/list.vue 的访问路径是什么?

路程由的 base 配置

如果项目不是部署在域名根目录下,需要配置 router.base

// nuxt.config.js

export default {
  router: {
    base: process.env.ROUTER_BASE,
  },
};

接口请求

由于 SSR,Nuxt.js 的请求库是在服务器端实例化的。所以不能简单的像单页应用一样,封装一个请求模块导出使用。这样使 Nuxt.js 在不同用户访问时共用一个请求实例,会出现用户数据泄漏的问题。

社区提供了 @nuxt/asiox 模块来处理这个问题。

使用:

  1. 安装 @nuxt/asiox

  2. 配置

    // nuxt.config.js
    
    export default {
      modules: ["@nuxtjs/axios"],
      axios: {
        // Axios 选项
      },
    };
  3. 使用方法

    https://axios.nuxtjs.org/usage

    在 plugin 中添加拦截器:https://axios.nuxtjs.org/extend

为了方便在服务器和浏览器中方便和有统一的方法对 cookie 进行操作,我们可以使用社区提供的 cookie-universal-nuxt 包。

NPM

// nuxt.config.js

export default {
  modules: [
    ['cookie-universal-nuxt', {
      // options
    }
  ]
}

它能在 Nuxt.js 的上下文 contextapp 和 Vue 组件实例中添加 $cookies 对象,其中有对 cookie 的操作方法:

// nuxt middleware
export default ({ app }) => {
  app.$cookies.set("cookie-name", "cookie-value", {
    path: "/",
    maxAge: 60 * 60 * 24 * 7,
  });
};

// client
this.$cookies.set("cookie-name", "cookie-value", {
  path: "/",
  maxAge: 60 * 60 * 24 * 7,
});

问答: cookit 设置成 httpOnly  要在哪一端获取 cookit , nodejs or 浏览器?

asyncData() 和 fetch()

Nuxt.js 为 Vue 添加了两个钩子

asyncData()

  • 限于页面组件
  • 它可以在服务端或浏览器端渲染页面前端调用,渲染组件前获取数据,返回的对象合并到前组件的 data
<template>
  <div>
    <h1>{{ post.title }}</h1>
    <p>{{ post.description }}</p>
  </div>
</template>

<script>
export default {
  async asyncData(context) {
    // 服务器端
    if (process.server) {
      const { req, res, beforeNuxtRender } = context;
    }
    // 浏览器端
    if (process.client) {
      const { from, nuxtState } = context;
    }

    const post = await axios.get(`https://api.nuxtjs.dev/posts/${params.id}`);
    return { post };
  },
};
</script>

fetch()

  • 页面组件和非页面组件可用
  • 什么时候被调用:
    • ⚠️  组件每次加载前被调用(在服务端或切换至目标路由之前),渲染组件前填充应用的状态树(store)数据
    • 同 methods 一样被调用,这时可以于改变组件的 data

fetch 方法来获取数据填充应用的状态树(vuex)。为了让获取过程可以异步,你需要返回一个 Promise,Nuxt.js 会等这个 promise 完成后再渲染组件,如:

<template>
  <h1>Stars: {{ $store.state.stars }}</h1>
</template>

<script>
export default {
  async fetch({ store, params }) {
    let { data } = await axios.get("http://my-api/stars");
    store.commit("setStars", data);
  },
};
</script>

让 query 变更也触发 asyncData 和 fetch

// pages/somepage.vue

<template>
  <h1>A Page</h1>
</template>

<script>
export default {
  watchQuery: true,
};
</script>

问答:asyncData 在什类型的组件中使用,页面组件 / 非页面组件?

Store(VUEX)

state 使用 function 设置默认值。 Object 是引用类型数据,在服务器端会在多个用户访问时使用同一个对象引用来初始化 store。

// store/something.js

export const state = () => ({
  list: [],
});

export const mutations = {
  add(state, text) {
    state.list.push({
      text,
      done: false,
    });
  },
  remove(state, { todo }) {
    state.list.splice(state.list.indexOf(todo), 1);
  },
  toggle(state, todo) {
    todo.done = !todo.done;
  },
};

其他最佳实践

UI 库的按需加载

减少静态资源的 size。

Element UI 和 Vant UI 的示范:

// nuxt.config.js

export default {
  build: {
    transpile: [/^element-ui/, /vant.*?less/],
    babel: {
      // Vant UI
      plugins: [
        [
          "import",
          {
            libraryName: "vant",
            style: (name) => `${name}/style/less`,
          },
          "vant",
        ],

        // Element UI
        [
          "component",
          {
            libraryName: "element-ui",
            styleLibraryName: "theme-chalk",
          },
        ],
      ],
    },
  },
};
<template>
  <el-button>Button</el-button>
</template>

<script>
import Vue from "vue";
import { Button } from "element-ui";
Vue.use(Button);
</script>

媒体资源 懒加载

使用  nuxt-lazy-load  在用户需要看见媒体时才加载资源。 https://gitlab.com/broj42/nuxt-lazy-load

// nuxt.config.js

export default {
  modules: ["nuxt-lazy-load"],
};

默认配置所有媒体开启懒加载。

背景图片的懒加载:

<div lazy-background="~/assets/images/background-image.jpg">Content</div>

非懒加载的 img 标签加 data-not-lazy,避免出现一些 bug,比如图片裁切组件等等:

<audio controls="controls" data-not-lazy>
  <source type="audio/mpeg" src="audio.mp3" />
</audio>

服务器端日志

添加服务器端日志,方便排查日志。 使用 nuxt-winson-log

配置:

// nuxt.config.js

export default {
  modules: [["nuxt-winston-log"]],
  winstonLog: {
    useDefaultLogger: false,
    loggerOptions: {
      format: combine(
        label({ label: "Nuxt Server" }),
        timestamp(),
        printf(({ level, message, label, timestamp }) => {
          return `${timestamp} [${label}] ${level}: ${message}`;
        })
      ),
      transports: [
        new transports.Console(),
        new transports.File({
          filename: path.resolve(
            process.cwd(),
            "./logs",
            `${process.env.NODE_ENV}.log`
          ),
          maxsize: 5 * 1024 * 1024, // 单个日志文件大小
          maxFiles: 20, // 最大文件数
        }),
      ],
    },
  },
};

使用

// pages/page-a.vue

export default PageA {
  asyncData(context) {
    if(process.server) {
      context.$winstonLog.info('Hello from asyncData on server')
    }
  }
}
← Snapdrop 跨平台内网传输工具Commitlint 使用入门 →

评论 · 0