Udemy 13. フロントエンドのログイン認証 #07
2020年11月03日に公開

【Nuxt.js】エラーページを作成する

今回達成すること

今回は、Nuxt.js側でエラーが発生した場合に表示するエラーページを作成します。

完成イメージ

2020-10-29 12-18-55

エラーレイアウトを作成する

まず初めに、「layouts」ディレクトリ内にerror.vueを作成します。

root $ touch front/layouts/error.vue

エラーの受け取り

Nuxt.jsは、レイアウトディレクトリにerror.vueがある場合、エラーが発生したときに、自動でこのファイルを使用します。
発生したエラーは、propsで受け取ります。

error.vueに追加しましょう。

front/layouts/error.vue
<template>
  <div>
    errorが発生しました
    {{ error }}
  </div>
</template>

<script>
export default {
  props: {
    error: {
      type: Object,
      default: null
    }
  }
}
</script>

エラーページのレイアウトを指定する

エラーレイアウトは、「layouts」ディレクトリ内にありますが、ページファイルと同じように扱います。

レイアウトを指定しない場合、default.vueが使用されます。

2020-10-12 07-42-49

今現在、default.vueはログイン後のレイアウトとして使用しています。

そこで、空白のレイアウトであるlogout.vueをエラーページのレイアウトファイルとして使用します。

ログアウトページのレイアウト名を変更する

logout.vueは、ログアウトページ以外にも使用されるようになりましたので、名前をnone.vueに変更します。

root $ mv front/layouts/logout.vue front/layouts/none.vue

Vuetifyを使用するので、名前を変更したnone.vue<v-app>タグを追加します。

front/layouts/none.vue
<template>
  <!-- v-app 追加 -->
  <v-app>
    <nuxt />
  </v-app>
</template>

名前を変えたので、ログアウトページのレイアウト指定を変更します。

front/pages/logout.vue
<template>
  <div />
</template>

<script>
export default {
  // noneに変更
  layout: 'none',
  // 削除
  // layout: 'logout',
  async beforeCreate () {
    await this.$auth.logout()
    this.$router.replace('/')
  }
}
</script>

エラーページのレイアウトを指定する

エラーページerror.vueのレイアウトを指定します。

front/layouts/error.vue
...
<script>
export default {
  // 追加
  layout: 'none',
  props: {
    error: {
      type: Object,
      default: null
    }
  }
}
</script>

確認しよう

存在しないURLにアクセスし、404エラーを出して確認しましょう。

エラー時のレイアウトが変更できています。

2020-10-12 07-55-28

エラーページを構築する

下準備ができたので、エラーページを構築していきます。

エラーページには、

  • エラーナンバー
  • エラーメッセージ
  • トップページへのリダイレクトリンク

を含めます。

エラーナンバーを表示する

エラーナンバーは、errorオブジェクトのstatusCodeに入っています。

算出プロパティを追加し、HTML内を丸っと書き換えます。

front/layouts/error.vue
<template>
  <!-- 追加 -->
  <v-container fill-height>
    <v-row>
      <v-col cols="12">
        <v-card-title class="justify-center">
          {{ status }}
        </v-card-title>
      </v-col>
    </v-row>
  </v-container>
  <!-- 削除 -->
  <!-- <div>
    errorが発生しました
    {{ error }}
  </div> -->
</template>

<script>
export default {
  layout: 'none',
  props: {
    error: {
      type: Object,
      default: null
    }
  },
  // 追加
  computed: {
    status () {
      return this.error.statusCode
    }
  }
}
</script>

画面にはエラーコードが出力されました。

2020-10-29 10-53-40

エラーメッセージを表示する

エラーメッセージは、受け取ったメッセージを日本語に変換して表示します。

i18nja.jsonに新しい翻訳を追加します。

front/locales/ja.json
{
  ...
  "pages": {
    ...
    },
    "project": {
      ...
    }
  },
  // 追加
  "error": {
    "Unauthorized": "認証に失敗しました",
    "This page could not be found": "ページが見つかりません",
    "Network Error": "サーバーで問題が発生しました"
  }
}

error.vueに、messageの算出プロパティを追加します。

  • 翻訳した日本語が存在するエラーメッセージには日本語メッセージを出力し、
  • そうでない場合は英語のエラーメッセージを出力します。
front/layouts/error.vue
<template>
  ...
          {{ status }}
        </v-card-title>
        <!-- 追加 -->
        <v-card-text class="text-center">
          {{ message }}
        </v-card-text>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  ...
  computed: {
    status () {
      return this.error.statusCode
    },
    // 追加
    message () {
      const msg = this.error.message
      const path = `error.${msg}`
      return this.$te(path) ? this.$t(path) : msg
    }
  }
}
</script>
  • this.$te(<パス>) … 引数のパスが存在する場合trueを返すi18nのメソッド。

    参考: Vue i18n

日本語化されたエラーメッセージが出力されました。

2020-10-29 11-00-05

iconを追加する

デコレーションとして、メッセージの下に申し訳なさそうなアイコンを追加します。

front/layouts/error.vue
<template>
  ...
          {{ message }}
        </v-card-text>
        <!-- 追加 -->
        <v-card-actions class="justify-center">
          <v-icon>
            mdi-emoticon-sick-outline
          </v-icon>
        </v-card-actions>
      </v-col>
    </v-row>
  </v-container>
</template>
...

リダイレクトリンクを追加する

エラー発生後は、ユーザーをホームに移動させる必要があるため、移動リンクを追加します。

401認証エラーの場合は、エラーページがレンダリングされる前に

  • Cookieのトークン
  • ローカルストレージの有効期限
  • Vuexのユーザーオブジェクト

の全てを綺麗にし、ログイン前の状態に戻します。

front/layouts/error.vue
<template>
  ...
        </v-card-actions>
        <!-- 追加 -->
        <v-card-actions class="justify-center">
          <v-btn
            icon
            x-large
            color="myblue"
            @click="redirect"
          >
            <v-icon>
              mdi-home
            </v-icon>
          </v-btn>
        </v-card-actions>
        <!-- ここまで -->
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  ...
  computed: {
    ...
  },
  // 追加
  async created () {
    // 認証エラーの場合はログアウト処理を行う
    if (this.status === 401) {
      await this.$auth.logout()
    }
  },
  methods: {
    redirect () {
      return this.$route.name === 'index'
        ? this.$router.go() : this.$router.push('/')
    }
  }
}
</script>

トップページでエラーが発生した場合

トップページでエラーが発生した場合、遷移先と現在のURLが一致しているため$router.push('/')ではページ遷移できません。

そこで、トップページの場合は$router.go()を使ってリロード処理を行います。

$router.go()は、本来引数に数字を渡し、ページを戻したり進めたりするVue.jsのメソッドです。

  • $router.go(1) … 一つ先のページに進む。
  • $router.go(-1) … 一つ前のページに戻る。

引数を省略した場合は、現在のページがリロードされます。

これは、内部的にJavaScriptのwindow.history.goが呼ばれるため、その挙動と一致するためです。

go()メソッド は、0パラメータまたは値を指定せずに呼び出すと、現在のページが再読み込みされます。

引用: https://developer.mozilla.org/en-US/docs/Web/API/History

これで、エラーページが完成しました。

意図的に認証エラーを発生させる

認証エラー時の挙動を確認するには、ログインした状態でミドルウェアのauthenticator.jsにVuexのユーザーをnullにするコードを差し込みます。

front/middleware/authenticator.js
export default async ({ $auth, store, route, redirect }) => {
  // トップページかつユーザーが存在しない場合、何もしない(layouts/welcome.vue表示のため)
  if (route.name === 'index' && !$auth.isUserPresent()) {
    return false
  }

  // テストコードの追加
  store.dispatch('getCurrentUser', null)
  ...
}

ページ遷移を行うと401エラーが発生します。

この時に、Cookieのトークンとローカルストレージが削除されていれば実装がうまくいっています。

2020-10-29 12-26-09

確認が取れたら必ず、テストコードは削除しておいてください。

front/middleware/authenticator.js
export default async ({ $auth, store, route, redirect }) => {
  // トップページかつユーザーが存在しない場合、何もしない(layouts/welcome.vue表示のため)
  if (route.name === 'index' && !$auth.isUserPresent()) {
    return false
  }

  // 削除
  // store.dispatch('getCurrentUser', null)
}

ログインページにエラーハンドリングを追加する

エラーページが完成したので、ログイン失敗時のエラー処理を行います。

エラー処理メソッドは、共通メソッドとしてmyInject.jsに作成します。

front/plugins/myInject.js
class MyInject {
  constructor (ctx) {		// app => ctxに書き換え
    // this.app = app   // 削除
    this.app = ctx.app  // 変更
    // 追加
    this.error = ctx.error
  }

  ...
  // プロジェクトリンク
  projectLinkTo (id, name = 'project-id-dashboard') {
    return { name, params: { id } }
  }

  // 追加
  // エラーハンドリング
  errorHandler ({ status, statusText }) {
    return this.error({ statusCode: status, message: statusText })
  }
}

// コンテキルトにerrorを追加
export default ({ app, error }, inject) => {
  // { app, error }に書き換え
  inject('my', new MyInject({ app, error }))
}

作成したerrorHandler()メソッドを、login.vueのログイン失敗時の処理に追加します。

front/pages/login.vue
...
<script>
export default {
  ...
  methods: {
    ...
    // ログイン失敗
    authFailure ({ response }) {
      // 書き換え
      return (response.status === 404)
        ? this.$store.dispatch('getToast', { msg: 'ユーザーが見つかりません😷' })
        : this.$my.errorHandler(response)
      // 削除
      // if (response.status === 404) {
      //   this.$store.dispatch('getToast', { msg: 'ユーザーが見つかりません😷' })
      // }
    }
  }
}
</script>

確認してみよう

Railsだけのサーバーを停止し、ログイン時に404以外のエラーが出るようにします。

root $  docker-compose stop api

この状態でログインを行なってください。

エラー画面に遷移すれば正しくメソッドが動いている証拠です。

2020-10-29 17-54-57

確認が取れたら全てのコンテナを削除しておきましょう。

root $ docker-compose down

コミットしとく

今回の実装は以上です。

コミットしておきましょう。

root $ cd front
front $ git add -A && git commit -m "add_layouts_error.vue" && cd ..
root $

まとめ

今回はエラーページの実装を行いました。

Nuxt.jsは「layouts」ディレクトリにerror.vueが存在する場合、自動で表示してくれます。

細かい設定などいらず、とても便利ですね。

一点だけ注意点は、error.vueは「layouts」ディレクトリ内で管理するが、ページVueファイルと同じように扱うということだけ覚えておきましょう。

ログイン認証 最終話予告

さて次回は、Railsにデモプロジェクト一覧を取得するコントローラーを作成しNuxt.jsで表示させます。

そして、ここまでの変更をデプロイし、ログイン認証の実装を終わります。

次回でログイン認証完走です。ご期待あれー。

あなたの力になれること
私自身が独学でプログラミングを勉強してきたので、一人で学び続ける苦しみは痛いほど分かります。そこで、当時の私がこんなのあったら良いのにな、と思っていたサービスを立ち上げました。周りに質問できる人がいない、答えの調べ方が分からない、ここを聞きたいだけなのにスクールは高額すぎる...。そんな方に向けた単発・短期間メンターサービスを行っています。下のサービスへお進みください。
独学プログラマのサービス
次の記事はこちら
1. このカテゴリーの歩き方 #01
【お知らせ】UdemyでRails × Nuxt.jsの動画を公開することになりました
1. このカテゴリーの歩き方 #02
アプリケーション仕様書
1. このカテゴリーの歩き方 #03
このカテゴリーの歩き方(まずはここをチェック)
1. このカテゴリーの歩き方 #04
(Docker+Rails6+Nuxt.js+PostgreSQL)=>Heroku 環境構築~デプロイまでの手順書
2. Docker入門 #01
Docker for Macをインストールする手順
2. Docker入門 #02
分かるDocker解説。仮想環境・コンテナ・Dockerイメージ・Dockerfileとは何か?
2. Docker入門 #03
分かるDocker解説。DockerComposeとは何か?
3. Dockerを使ったRails+Nuxt.js環境構築 #01
【Docker+Rails6+Nuxt.js】今回作成するアプリの開発環境の全体像を知ろう
3. Dockerを使ったRails+Nuxt.js環境構築 #02
【MacOS】Homebrew経由でGitをインストールする方法
3. Dockerを使ったRails+Nuxt.js環境構築 #03
Rails6を動かすAlpineベースのDockerfileを作成する(AlpineLinuxとは何か)
3. Dockerを使ったRails+Nuxt.js環境構築 #04
Nuxt.jsを動かすAlpineベースのDockerfileを作成する(C.UTF-8とは何か)
3. Dockerを使ったRails+Nuxt.js環境構築 #05
.envファイルを使ったdocker-compose.ymlの環境変数設計
3. Dockerを使ったRails+Nuxt.js環境構築 #06
Rails6・Nuxt.js・PostgreSQLを動かすdocker-compose.ymlファイルを作成する
3. Dockerを使ったRails+Nuxt.js環境構築 #07
docker-compose.ymlを使ってRails6を構築する(PostgreSQLパスワード変更方法)
3. Dockerを使ったRails+Nuxt.js環境構築 #08
docker-compose.ymlを使ってNuxt.jsを構築する
4. 複数プロジェクトのGit管理 #01
複数プロジェクトで行うGit管理の全体像を理解しよう(Gitサブモジュール解説)
4. 複数プロジェクトのGit管理 #02
【Git】既存の子ディレクトリをサブモジュール管理に変更する手順
4. 複数プロジェクトのGit管理 #03
【GitHub】秘密鍵の生成・公開鍵を追加・SSH接続するまでを画像で分かりやすく
4. 複数プロジェクトのGit管理 #04
【GitHub】リモートリポジトリの追加・サブモジュールのリンク設定を行う
5. RailsAPI×Nuxt.js初めてのAPI通信 #01
【Rails6】"Hello" jsonを返すコントローラを作成する
5. RailsAPI×Nuxt.js初めてのAPI通信 #02
【Nxut.js】axiosの初期設定を行う(baseURL・browserBaseURLを解説)
5. RailsAPI×Nuxt.js初めてのAPI通信 #03
【Rails6】Gem rack-corsを導入してCORS設定を行う(オリジン・CORSとは何か)
6. Heroku.ymlを使ったDockerデプロイ #01
デプロイ準備。Herokuへ新規会員登録を行いHerokuCLIをインストールする
6. Heroku.ymlを使ったDockerデプロイ #02
heroku.yml解説編。Docker環境のRails6をHerokuにデプロイする(1/2)
6. Heroku.ymlを使ったDockerデプロイ #03
HerokuCLI-manifestのデプロイ解説編。Docker環境のRails6をHerokuにデプロイする(2/2)
6. Heroku.ymlを使ったDockerデプロイ #04
Dockerfile解説編。Docker環境のNuxt.jsをHerokuにデプロイする(1/2)
6. Heroku.ymlを使ったDockerデプロイ #05
デプロイ完結編。Docker環境のNuxt.jsをHerokuにデプロイする(2/2)
7. モデル開発事前準備 #01
【Rails6】application.rbの初期設定(タイムゾーン・I18n・Zeitwerk)
7. モデル開発事前準備 #02
【Rails6】モデル開発に必要なGemのインストールとHirb.enableの自動化
7. モデル開発事前準備 #03
【Docker+Rails】A server is already running. Check /tmp/pids/server.pidエラーの対応
7. モデル開発事前準備 #04
【Docker】<none>タグのイメージを一括削除する & Rails .gitignoreの編集
8. ユーザーモデル開発 #01
Railsユーザーモデル作成。テーブル設計・ユーザー認証設計を理解する
8. ユーザーモデル開発 #02
Railsユーザーモデルのバリデーション設定(has_secure_password解説)
8. ユーザーモデル開発 #03
Railsバリデーションエラーメッセージの日本語化(ja.yml設定方法)
8. ユーザーモデル開発 #04
EachValidatorクラスのカスタムバリデーション設定(Rails6/lib以下読込)
8. ユーザーモデル開発 #05
Rails環境ごとにSeedデータ切り替えるseeds.rbの書き方
8. ユーザーモデル開発 #06
Rails6から導入された並列テストを理解する
8. ユーザーモデル開発 #07
Railsユーザーモデルバリデーションテスティング(name/email/password)
8. ユーザーモデル開発 #08
Nuxt.jsからRailsのユーザーテーブルを取得しHerokuにデプロイする
9. Nuxt.jsフロント開発事前準備 #01
【Nuxt.js2.13超解説】バージョンアップ手順と6つの新機能+2つの変更点
9. Nuxt.jsフロント開発事前準備 #02
Docker AlpineベースのNode.js上で動くNuxt.jsにVuetifyを導入する
9. Nuxt.jsフロント開発事前準備 #03
VuetifyにカスタムCSSを導入してオリジナルブレイクポイントを作る
9. Nuxt.jsフロント開発事前準備 #04
Nuxt.jsにnuxt-i18nを導入して国際化に対応する
10. ログイン前のレイアウト構築 #01
Nuxt.jsのレイアウト・ページ・コンポーネントの役割を理解しよう
10. ログイン前のレイアウト構築 #02
Nuxt.js ウェルカムページを構成するコンポーネントファイル群を作成しよう(1/4)
10. ログイン前のレイアウト構築 #03
Nuxt.js ウェルカムページにアイキャッチ画像・アプリ名・メニューボタンを表示しよう(2/4)
10. ログイン前のレイアウト構築 #04
Nuxt.js addEventListenerでスクロールを検知しツールバーの色を変化させよう(3/4)
10. ログイン前のレイアウト構築 #05
Nuxt.js ウェルカムページをレスポンシブデザインに対応させよう(4/4)
10. ログイン前のレイアウト構築 #06
Nuxt.js 会員登録ページのレイアウトファイルを作成しよう(1/4)
10. ログイン前のレイアウト構築 #07
Nuxt.js 名前、メール、パスワードのコンポーネントファイルを作成しよう(2/4)
10. ログイン前のレイアウト構築 #08
Nuxt.js 親子コンポーネント間の双方向データバインディングを実装する(3/4)
10. ログイン前のレイアウト構築 #09
Nuxt.js Vuetifyのv-text-fieldを使った会員登録フォームのバリデーション設定(4/4)
10. ログイン前のレイアウト構築 #10
Nuxt.js ログインページ実装とHerokuデプロイまで(router. replaceとpushの違いとは)
11. ログイン後のレイアウト構築 #01
Nuxt.js ログイン後のツールバーを作成しよう(inject解説)
11. ログイン後のレイアウト構築 #02
Nuxt.js アカウントメニューページ・ログアウト機能を実装しよう(nuxt-child解説)
11. ログイン後のレイアウト構築 #03
Nuxt.js ログイン後のトップページにプロジェクト一覧を表示しよう
11. ログイン後のレイアウト構築 #04
Nuxt.js プロジェクトページにVuetifyのナビゲーションドロワーを追加しよう
11. ログイン後のレイアウト構築 #05
Nuxt.js paramsIDからプロジェクトを検索してVuexに保存しよう
12. サーバーサイドのログイン認証 #01
JWTとは何か?(ruby-jwtのインストール)
12. サーバーサイドのログイン認証 #02
【Rails×JWT】ログイン認証解説とJWT初期設定ファイルの作成
12. サーバーサイドのログイン認証 #03
【Rails×JWT】トークン発行とデコードを行うAuthTokenクラスの作成
12. サーバーサイドのログイン認証 #04
【Rails×JWT】 ログイン判定を行うAuthenticatorモジュールの作成
12. サーバーサイドのログイン認証 #05
【Rails×JWT】UserクラスからJWTを扱うTokenizableモジュールの作成
12. サーバーサイドのログイン認証 #06
【Rails×JWT】AuthTokenクラスとAuthenticatorモジュールをテストする
12. サーバーサイドのログイン認証 #07
【Rails×JWT】JWTをCookieに保存するログインコントローラーの実装
12. サーバーサイドのログイン認証 #08
【Rails×JWT】ログインコントローラーのテストとHerokuデプロイ
13. フロントエンドのログイン認証 #01
【Rails×Nuxt.js】クロスオリジン通信でのCookie共有設定
13. フロントエンドのログイン認証 #02
【Nuxt.js】Railsからのログイン成功レスポンスをVuexに保存する
13. フロントエンドのログイン認証 #03
【Nuxt.js】ローカルストレージの有効期限を暗号化する(crypto-js解説)
13. フロントエンドのログイン認証 #04
【Nuxt.js】JWT有効期限内のユーザーをログインしたままにする実装
13. フロントエンドのログイン認証 #05
【Nuxt.js】ログイン前後のリダイレクト処理をミドルウェアで実装する
13. フロントエンドのログイン認証 #06
【Nuxt.js】ログイン失敗時のトースターをグローバルイベントとして作成する
13. フロントエンドのログイン認証 #07
【Nuxt.js】エラーページを作成する
13. フロントエンドのログイン認証 #08
【Rails×Nuxt.js】デモプロジェクトの作成とHerokuデプロイ(ログイン認証完)
14. 本番環境への対応 #01
【Rails×Nuxt.js】SafariのクロスサイトCookie保存拒否に対応する
SPA開発
ブログ構築
小ネタ集