差點就要開天窗了...,今天整理之前的介紹,實際用 Laravel 和 Nuxt 做了最雛形的網站包含了「註冊」、「登入」和「登出」功能,有興趣的鐵人大大可以到 GitLab 看完整的 code ! 今天主要是 Nuxt 部分的介紹。

一開始我們先準備好下列的 page components:

page component

register.vue login.vue 當中,除了昨天介紹過的表單寫法之外,主要的重點在 asyncData 的部份,當頁面載入的時候要先檢查是否已經存有 token (即登入的狀態),若已經登入就要跳轉至首頁:

    async asyncData({ store, redirect }) {
        const hasToken = store.getters['hasToken'];
        if (hasToken) {
            redirect('/');
        // register 和 login 註冊的表單

login.vueasyncData 裡面,用來判斷受否已經登入以及登入者的角色並用來跳轉頁面:

export default {
  name: 'index',
  async asyncData({ app, store, redirect }) {
    const hasToken = store.getters.userToken;
    if (hasToken) {
      const isManager = store.getters['user/isManager'];
      const isUser = store.getters['user/isUser'];
      if (isManager) {
        redirect('/manager');
      if (isUser) {
        redirect('/user');
    return {};

剛剛上面有用到 Vuex store module 的資料,所以建立下面的 store module:

module

為了方便控管,同時也是藉由 nuxtServerInit 方法儲存 cookie 相關資料。在今天的例子只有 userToken 一個資料:

const state = () => {
    return {
        userToken: undefined,
const actions = {
    nuxtServerInit({ commit }, { req }) {
        const hasCookieInReq = !!req.headers.cookie;
        if (hasCookieInReq) {
            try {
                const allCookies = cookieparser.parse(req.headers.cookie);
                const token = allCookies['auth-token'];
                commit('SET_USER_TOKEN', token);
            } catch (error) {
                // ...
    // ...

user.js 的 getters 中,除了取得儲存的 user 同時也特別增加了 hasUser,這是用來確認目前的 store 裡面有沒有資料,假若頁面可以取得 token 但卻沒有 user 資料,代表 Vuex 的資料揮發了,需要透過 API 重新取得資料。

const state = () => {
    return {
        user: {
            name: undefined,
            email: undefined,
            role: undefined,
// ... 
const getters = {
    hasUser(state) {
        return !!state.user.role;
    // ...

由於任一個 page component 都可能受到 Vuex 資料揮發的影響,因此我們在 middleware 底下加入 auth.js,並且將它掛在 nuxt.config.jsrouter 屬性下,這樣載入任何一頁都會先檢查是否存在 「有 userToken 但是沒有 user」 的情況:

import { validators } from '@/configs/api/user';
export default async ({ app, store }) => {
    const hasToken = store.getters.hasToken;
    const hasUser = store.getters['user/hasUser'];
    if (hasToken) {
        if (!hasUser) {
            try {
                const { data } = await app.$api(validators.AccessUserRequest, { userId: null });
                await store.dispatch('user/setUser', data);
            } catch (error) {
                await store.dispatch('removeUserToken');

順帶一提,因為 JWT 是有時效性的,所以假若 token 已經過期,無法取得 user 資料時,我們會將 userToken 清除,讓使用者可以再次登入。

至目前為止「註冊」和「登入」功能完成了,但「路由依角色跳轉」功能只完成一半 (首頁的部份),我們很常會直接打上特定網址瀏覽頁面,所以另一半是要撰寫在 pages/manager 底下的頁只有管理者可以瀏覽,pages/user則相反,所以我們增加下列資檔案:

  • page components:
  • 在上述兩個 middlewares 中,我們都只檢驗個別角色而已,同時只要有不符合的情況一律轉到 pages/index.vue,這樣假設未來系統角色變多了,實際判斷要跳轉的角色首頁都在 pages/index.vue 做就可以了。

    export default async ({ store, redirect }) => {
        const hasToken = store.getters.hasToken;
        const isUser = store.getters['user/isUser'];
        if (!(hasToken && isUser)) {
            redirect('/');
    

    最後我們來補上「登出」功能就可以收工啦! 因為不論是管理者或是一般使用者都要有登出功能,在 pages/manager.vuepages/user.vue 各寫一次也可以,但同樣的假設未來角色變多就很麻煩,所以我們可以把「登出」寫在 layouts,這樣在 neast page components 要修改的幅度就少很多了!

    下面是 layout/baseLayout.vue

    <template>
        <Row class="base-layout">
            <Row class="head">
                <!-- ... -->
                <button class="logout-btn" @click="logoutAction">登出</button>
            <Row class="body">
                <nuxt />
    </template>
    <script>
    import { mapActions } from 'vuex';
    import { validators } from '@/configs/api/user';
    export default {
        name: 'BaseLayout',
        data() {
            return {
                storeUser: undefined,
        methods: {
            ...mapActions(['removeUserToken']),
            async logoutAction() {
                try {
                    const { status, code } = await this.$api(validators.LogoutRequest, {});
                    const successLogout = status && code === 200;
                    if (successLogout) {
                        this.removeUserToken();
                        this.redirectToIndex();
                } catch (error) {
            redirectToIndex() {
                this.$router.push({ name: 'index' });
        async asyncData({ store }) {
            const storeUser = store.getters['user/storeUser'];
            return {
                storeUser,
    </script>
    

    pages/manager.vuepages/user.vue 各加入 layouts 的設定:

        // ...
        layout: 'baseLayout',