📝

Nuxtでaxiosの404がdevとbuildで挙動が違う

2021/09/09に公開

まえがき

Nuxt で asyncData や fetch 関数で axios を使って通信するは特に気にすることではないのですが、 SPASSR などで Nuxt が提供する関数以外の関数で通信を行った場合に、 404 でうまいことエラー画面に遷移してくれないことがあります。

Axios Module

createdmounted などのような Vue.js が提供する関数や、 @click="clickHandler" のようなイベントハンドラー内だと

$ nuxt dev

では axios で 404 をキャッチした場合は layouts/error.vue に遷移されるが、

$ nuxt build or generate
$ nuxt start

だと、遷移されません。

これをどうやって挙動を同じにするかをメモしておきます。

nuxt.config.js の設定は以下のような状態になります。

export default {
  ssr: false,
  target: 'server',     // defalut value
}

ssr プロパティ - NuxtJS

target プロパティ - NuxtJS

サンプルページを作る

pages/request-error-sample.vue

script 部分だけを書いています。

created や click イベントハンドラーのところに axios を使った通信処理を書いています。

どちらの場合も nuxt dev ではエラーページに遷移し、 nuxt build ではエラーページに遷移してくれません。

import Vue from 'vue';
import { mapActions } from 'vuex';

export type DataType = {
  value: string;
};

export default Vue.extend({
  data(): DataType {
    return {
      value: 'initial value',
    };
  },
  mounted(): void {
  },
  async created() {
    // 404 エラーが返ってくる想定
    this.value = await this.$axios.$get(`http://localhost:4000/user/hisasann2`);
  },
  methods: {
    ...mapActions({}),
    async clickHandler() {
        // 404 エラーが返ってくる想定
      this.value = await this.$axios.$get(
        `http://localhost:4000/user/hisasann2`
      );
    },
  },
});

plugins/axios.ts

plugin を書くことで、 axios の 404 でエラーページに遷移するように error 関数を使う。

$axios.onResponseError にフックすることでレスポンスのエラーを監視してステータスコードがどうだったらみたいな処理は書けます。

ただし、たとえば 404 で監視すると axios の通信で 404 が発生したら無条件にエラーページに行ってはしまうので、そこは気にする必要があるかもしれません。

import { Plugin } from '@nuxt/types';
import { AxiosError } from 'axios';

const axiosPlugin: Plugin = ({ $axios, app, req, error }): void => {
  /**
   * $axios.onResponseError
   */
  $axios.onResponseError((response: AxiosError): void => {
    console.log('$axios.onResponseError');
    // 通信エラー
    if (!response.response) {
      return;
    }

    const { status, statusText, config } = response.response;

    // 404
    if (status === 404) {
      const message = statusText;
      // ここで error 関数を呼ぶと axios の 404 が発生したら必ずエラーページに遷移する
      // error({ statusCode: status, message })
    }
  });
};

export default axiosPlugin;

plugins/error-handler.ts

このパターンは、 Vue.config.errorHandler にフックすることで、 axios の 404 だけにフォーカスせずに Vue のどんなタイミングでキャプチャされたかもコミコミで監視して error 関数を呼び出す方法です。

import axios from 'axios';
import Vue from 'vue';

export default function ({ error }: any) {
  // via https://qiita.com/clomie/items/73fa1e9f61e5b88826bc
  // Vue.config.errorHandler でフックできないものたちは以下の2パターンで監視するのもありです。
  window.addEventListener('error', (event) => {
    console.log('Captured in error EventListener', event.error);
  });
  window.addEventListener('unhandledrejection', (event) => {
    console.log('Captured in unhandledrejection EventListener', event.reason);
  });

  Vue.config.errorHandler = (err, vm, info) => {
    console.log(`Captured in Vue.config.errorHandler: ${info}`, err);

    if (axios.isAxiosError(err)) {
      if (!info.match(/(created|mounted)/)) {
        console.log('!created|mounted:', err);
        return;
      }
      console.log('created|mounted:', err);

      // axios のエラーで、且つ created や mounted イベントでキャプチャされた場合
      error({ statusCode: err.response?.request.status });
    } else {
      console.log('axiosのエラーではない:', err);
    }
  };
}

nuxt.config.js

export default {
  plugins: ['@/plugins/axios.ts', '@/plugins/error-hander.ts'],
}

axiosが通信するサーバーを作る

server.js

express を使った 404 を発生させるためのサーバーです。

const express = require('express');
const app = express();
const cors = require('cors');

app.use(cors());

const port = 4000;

app.get('/user/:userId', function (req, res, next) {
  console.log('get parameter server', req.params);
  if (req.params.userId !== 'hisasann') {
    res.status(404);
  }
  res.send('get parameter client: ' + req.params.userId);
});

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});
$ node server.js

Discussion