视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
Nuxt.js实现一个SSR的前端博客的示例代码
2020-11-27 21:51:03 责编:小采
文档

为什么要用Nuxt.js

公司现有的项目只有落地页是通过前端本身server读取pug文件进行服务端渲染的,当然是为了首屏加载速度以及SEO。Nuxt.js 是一个基于Vue.js的通用应用框架,预设了利用Vue.js开发服务端渲染的应用所需要的各种配置,只需要安装官方文档的要求进行开发,就可以很好的解决SSR的问题。我们以一个简单的博客为例,来实践一下Nuxt.js。

项目介绍

当前基于Nuxt.js的简化版博客,包括注册、登录、文章列表页面、文章详情页、以及用户列表页等几个页面,用户信息使用了Vux进行存储,异步数据使用了asyncData进行获取,配合了nuxtServerInit、cookie来处理刷新页面后Vux数据丢失的问题,同时使用了error模板页面处理常规错误,使用了中间件进行了简单的权限校验。该项目不足点,统一封装了axios的方法,但是没有考虑到服务端请求接口,token的处理。

目录结构

  •  assets: 资源文件。用于组织未编译的静态资源如 LESS、SASS或 JavaScript。
  • components: 组件。
  • layouts: page: 模板页面,默认为 default.vue可以在这个目录下创建全局页面的统一布局,或是错误处理页面页,需要提供一个nuxt 标签,类似于router-view
  • middleware: 中间件,放置自定义的中间件,会在加载组件之前调用。可以在页面中调用: middleware: '中间件名称'。
  • pages: 页面,index.vue 为根页面,Nuxt.js 框架读取该目录下所有的 .vue文件并自动生成对应的路由配置,如需要动态参数id,则可以添加_id的文件,必须是下划线加参数名。
  • plugin: 插件,用于组织那些需要在 根Vue.js应用实例化之前需要运行的 Javascript 插件。
  • static: 静态文件,静态文件目录 static用于存放应用的静态文件,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。
  • store: 用于组织vuex状态管理。具体使用请移步至 官网。
  • nuxt.config.js: nuxt.config.js文件用于组织Nuxt.js 应用的个性化配置,配置head,loading,css,plugins等。
  • Nuxt.js生命周期

    1. incoming Request 浏览器发出的请求)
    2. nuxtServerInit 服务端接受请求后,要检查当前有没有 nuxtServerInit配置项,如果有就执行这个函数
    3. store action 用来操作vuex
    4. middleware 可以做jWT等一些操作。
    5. validate() 检验参数,参数检验失败,可以在layout里的error里面进行捕捉。
    6. asyncData()& fetch() asyncData用来渲染组件,fetch用来渲染vuex
    7. Render

    Nuxt扩展以后的生命周期和方法以下:

    beforeCreate: ƒ beforeCreate()
    components: {NuxtLoading: {…}}
    computed: {isOffline: ƒ}
    context: {isStatic: false, isDev: true, isHMR: true, app: {…}, payload: undefined, …}
    created: ƒ created()
    data: ƒ data()
    head: {title: "nuxt-meituan-ssr", meta: Array(3), link: Array(1), style: Array(0), script: Array(0)}
    methods: {refreshOnlineStatus: ƒ, refresh: ƒ, errorChanged: ƒ, setLayout: ƒ, loadLayout: ƒ}
    mounted: ƒ mounted()
    nuxt: {…}
    render: ƒ render(h, props)
    router: VueRouter {app: Vue, apps: Array(1), options: {…}, beforeHooks: Array(2), resolveHooks: Array(0), …}
    watch: {nuxt.err: "errorChanged"}

    注意:

  • Vue.js生命周期的钩子只有beforeCreate和created会在服务端和客户端渲染。
  • 以上生命周期里都获取不到window对象。
  • asyncData和fetch我们可以拿到数据,不要尝试挂载数据到data上,此时获取不到this对象。
  • 开发总结

    如何修改默认启动端口?

    可以在package.json下面修改配置,如下。

    "config":{
     "nuxt":{
     "host":"127.0.0.1",
     "port":"3304"
     }
    }

    如何添加全局的样式?

    可以在assets里添加全局Css文件,如在assets下的Css文件夹目录下添加了一个index.css文件,然后在nuxt-config.js里配置该css文件路径即可。 css:['~assers/css/index.css']

    通过别名访问图片在template里是正确的,为何在Css设置背景图却报错?

    在css配置的是,需要将'~/'后面的'/'去除掉。

     <img src="~/static/logo.jpg"/> 
     backround-image:url('~static/logo.jpg');

    如何添加路由动画?

    同样,我们在Css文件里添加一些动画代码,一般样式会在其后面添加-active和-leave-active,其实和Vue动画形式一致。其中以page开头的动画,默认会作用于全部页面,如果想给特定的页面加动画,可以在对应的页面script里引用,如 transitions: 'bounce'即可。

    .page-enter-active, .page-leave-active {
     transition: opacity .3s
     }
     .page-enter, .page-leave-active {
     opacity: 0
     }
     .bounce-enter-active {
     animation: bounce-in .8s;
     }
     .bounce-leave-active {
     animation: bounce-out .5s;
     }
     @keyframes bounce-in {
     0% { transform: scale(1) }
     50% { transform: scale(1.01) }
     100% { transform: scale(1) }
     }
     @keyframes bounce-out {
     0% { transform: scale(1) }
     50% { transform: scale(1.01) }
     100% { transform: scale(1) }
     }

    路由参数如何传递?

    同Vue-router,有声明式和编程式两种方式,无非是标签变成了 router.push(...)

     nuxt-link :to="{name:'article',params:{id:1234}}" >声明式</nuxt-link>
     // 编程式
     this.$router.push({
     name:'article',
     params:{
     id:1234
     }
     })

    动态路由如何进行参数检验?

    Nuxt.js提供了一个validate的生命周期钩子,可以在此进行参数的校验。以文章详情校验id为例,我们需要判断传入的id是否是数字,可以像下面这样处理。

     validate({ params }) {
     return /^\d+$/.test(params.id)
     }

    如何添加404等错误页面?

    可以在layout下新建一个error.vue页面,内容如下,当访问一个不存在的页面的时候,或者参数检验失败的时候,或者我们在middleware中间件处理抛出异常的时候,都会跳转到该页面。

    <template>
     <div class="container">
     <h1 v-if="error.statusCode === 404">页面不存在</h1>
     <h1 v-else>应用发生错误异常</h1>
     <nuxt-link to="/">首 页</nuxt-link>
     </div>
    </template>
    
    <script>
    export default {
     props: ['error'],
     layout: 'blog' // 指定模板页面
    }
    </script>

    middleware中的文件抛出错误

    export default function({ store, error, redirect }) {
     if (!store.state.user.userInfo.auth) {
     error({
     message: '没有权限哦!',
     statusCode: 403
     })
     }
    }

    顶部进度条如何设置?

    loading 属性配置 可以在nuxt-config.js设置loading的颜色,使用了this. loading可能无法在created里立即使用。此种配置loading有严重的缺陷,无法知道真正的加载进度。也可以自定义加载组件,loading: '~components/loading.vue'。

    export default {
     mounted () {
     this.$nextTick(() => {
     this.$nuxt.$loading.start()
     setTimeout(() => this.$nuxt.$loading.finish(), 500)
     })
     }
     }

    异步数据如何获取?

    Nuxt.js提供了两个函数,asyncData和fetch函数。asyncData 获取组件的数据,fetch 在渲染页面之前获取数据填充应用的状态树(store)。

    asyncData可以使用promise也可以使用async函数,记住,此时返回的东西需要用一个对象进行包裹,不能挂载到data里,此时没有this对象。

    // 方式一
     asyncData({ app,params,route,query,error}) {
     return getUserlist({}).then(res => {
     let user = [];
     user = res.list
     console.log(user,'user')
     return {user}
     })
     .catch(err => {
     console.log(err)
     })
    },
    
    // 方式二
    async asyncData({ app }) {
     let data = await getUserlist({});
     let user = data.list;
     return { user }
    }

    fetch函数同上,可以使用promise也可以使用async函数,通常会commit一个mutation。

    export default {
     fetch ({ store, params }) {
     return axios.get('http://my-api/stars')
     .then((res) => {
     store.commit('setStars', res.data)
     })
     }
    }
    </script>
    // 或者使用 async 或 await 的模式简化代码如下:
    <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>

    如何动态修改title的内容?

    如果是写死的,可以直接修改head的配置。

     head() {
     return {
     // title: '',这里一旦声明,在asyncdata里修改也不起作用,直接以这个为准
     meta: [
     {
     hid: 'description', // nuxt.config 替换唯一标识 hid { hid: 'description', name: 'description', content: 'Nuxt.js project' }
     name: 'content',
     content: '文章详情'
     }
     ]
     }
     },

    如果是动态数据从数据源里获取,然后通过asynData里的app对象,动态修改head的title。

     asyncData({ app, params }) {
     const id = params.id;
     return getArticleDetail({ id })
     .then(result => {
     app.head.title = result.title;
     })
     .catch(err => {})
     }

    如何进行权限JWT验证?

    登录成功以后,我们会在cookie和Vuex中缓存token信息,当界面刷新的时候,会走store里的nuxtServerInit 函数,该函数仅在每个服务器端渲染中运,可以使用req.headers.cookie获取浏览器的cookie,再次更新store里的值,接着会走到中间件,中间件进行验证,如果有token信息则继续,没有则跳转到登录页。
    1. 为什么要在nuxtServerInit更新store的值?
    需要在middleware里使用,否则刷新后store里的值为空了。
    2. 客户端调用接口可以拿到token,服务器端如何拿到?
    可以通过nuxtServerInit里的req拿到请求信息的cookie,然后请求接口。
    3. 前后端分离,刷新的时候如何保证用户名、token等信息依然存在?
    可以像上面一样,每次取cookie的值再次更新store,但这样有一个问题,cookie可能会被篡改,后端代码需要做验证。也可以每次刷新重新通过token请求接口,更新用户信息。

    store代码

    import Vue from 'vue';
    import Vuex from 'vuex';
    import user from './modules/user';
    import { COOKIE_KEY } from '~/assets/js/constant.js';
    Vue.use(Vuex);
    const store = () =>
     new Vuex.Store({
     modules: {
     user
     },
     actions: {
     async nuxtServerInit({ commit, dispatch }, { req, app }) {
     if (req.headers.cookie) {
     let parsedResult = {};
     req.headers.cookie.split(';').forEach(cookie => {
     const currentCookie = cookie.split('=');
     parsedResult[currentCookie[0].trim()] = (currentCookie[1] || '').trim();
     });
     const userInfo = {
     name: parsedResult[COOKIE_KEY.NAME],
     token: parsedResult[COOKIE_KEY.TOKEN]
     };
     commit('user/setUserInfo',userInfo);
     }
     }
     }
     });
    
    export default store;

    中间件代码

    export default function({ store, error, redirect }) {
     if (!store.state.user.userInfo.token || !store.state.user.userInfo.name) {
     // error({
     // message: 'You are not connected',
     // statusCode: 403
     // })
     redirect('/');
     }
    }

    nginx部署

     npm run build
    选择build以后的四个文件: .nuxt, static, nuxt.config.js, package.json上传到服务器。
    pm2 pm2 start npm --name 'package.json.name' -- run start
    nginx配置

    查看网页源代码可以看到:

     server{
     listen 3000;
     server_name felix12345.club; 
     gzip on;
     gzip_buffers 32 4K;
     gzip_comp_level 6;
     gzip_min_length 100;
     gzip_types application/javascript text/css text/xml;
     gzip_disable "MSIE [1-6]\."; 
     gzip_vary on;
     proxy_buffer_size k;
     proxy_buffers 32 32k;
     proxy_busy_buffers_size 128k;
     location / {
     root /data/ww/nuxt;
     proxy_pass http://127.0.0.1:3002;
     proxy_set_header X-Real-IP $remote_addr;
     }
     }

    这样,使用Nuxt.js实现了一个服务端渲染的简易博客。

    在线访问地址: http://felix12345.club:3000/article/

    下载本文
    显示全文
    专题