ymmooot

Nuxt の plugin で route をリアクティブに扱う

概要

Nuxt.js の plugin に渡ってくる context から route を取り出しても、それは plugin が初期化される時点の route なので、その後 SPA 遷移によって変化していく route を捕捉できない。
ここでは Nuxt.js の middleware に頼らずに、plugin からリアクティブに route を扱う方法を書く。

実例

app に生えてる router の hook で変更を検知していく。
以下が url hash をリアクティブに扱う例。

hash.tsimport { Context } from '@nuxt/types';
import { defineNuxtPlugin, ref } from '@nuxtjs/composition-api';

const makePlugin = ({ app }: Context) => {
  const hash = ref('')
  app.router?.beforeEach((to, from, next) => {
    hash.value = to.hash
    next();
  });

  watch(hash, (newHash, oldHash) => {
    console.log(newHash, oldHash)
  })

  return { 
    hash,
  };
};

export default defineNuxtPlugin((ctx, inject) => {  
  inject('sample', makePlugin(ctx));
});

export type HashPlugin = ReturnType<typeof makePlugin>;

/*
setup() {
  const { $sample } = useContext()
  console.log($sample.hash.value) // #hoge
}
*/

以下がサインインページに遷移する前の URL path を SessionStorage に保存しておく例。
@vueuse/core を利用している。

signinRedirectPath.tsimport { Context } from '@nuxt/types';
import { defineNuxtPlugin, ref } from '@nuxtjs/composition-api';
import { useSessionStorage } from '@vueuse/core';

const sessionStorageKey = 'signInRedirectPath'

const makePlugin = ({ app }: Context) => {
  const path = useSessionStorage(sessionStorageKey, '');
  app.router?.beforeEach((to, from, next) => {
    if (to.name === 'signin') {
      path.value = from.path
    }
    next();
  });

  return { 
    get path() {
      return path.value
    },
    clear: () => {
      path.value = ''
    }
  };
};

export default defineNuxtPlugin((ctx, inject) => {  
  inject('signInRedirectPath', makePlugin(ctx));
});

export type SignInRedirectPath = ReturnType<typeof makePlugin>;

/*
setup() {
  const { $signInRedirectPath } = useContext()
  console.log($signInRedirectPath.path) // /hoge/  
}
*/

なお上記の2例では ReturnType で plugin の型を定義しているので、plugin 利用側が plugin の実装に依存する。
依存性を逆転させたい場合は interface を別途定義した上でそれを満たすようにすると良い。

まとめ

VueRouter のフックを使いたいからと、plugin と middleware に処理が分散するとコードを追いづらいしテストを書くのも面倒。
app.router のフックを利用すれば一箇所に記述をまとめられ、@nuxtjs/composition-api と組み合わせれば Ref として扱える。
コンポーネントでやるのと同じように watch や computed に利用できる上に、 @vueuse/core といった composable も利用できる。