📘

一日一処: Nuxtでlayoutが一生設定できなかった時の事実(自分のせい)

2024/02/16に公開

layoutの設定

Nuxtでは、それぞれのページにて、中身のコンテンツ以外の大枠となるレイアウトを設定することができる。このレイアウトは、通常defaultという名称で、すべてのページに反映される。ときには、特定のページにて、異なるレイアウトファイルを使いたいことがある。今回はその時に起きてしまった自分のミスを記述する。

特定ページでのレイアウト設定

特定のページでレイアウトを設定する方法は、おもに2つだ。
まず1つ目だ。

<script setup>
setPageLayout('custom')
</script>

setPageLayoutは、動的に変更する際に用いる。ただし、上記のような記述の場合、コンソールに警告が表示される。設定そのものは可能だが、一度defaultのレイアウトで描画された後に設定したレイアウトで再表示されるため、ページが瞬間的にちらつく。本来は、ページ生成前のmiddlewareなどで定義するのが適切とのことだ。普通に検索すると、関数のみ出てくるので、初見では、使う場所勘違いしてしまう。いわゆるNuxt Contextとして、middlewareで設定する。

export default defineNuxtRouteMiddleware((to) => {
  if (to.path === '/custom') {
    setPageLayout('custom')
  } else {
    setPageLayout('default')
  }
})

もう一つの方法は、ページ設定に組み込む方法だ。

<script setup>
definePageMeta({ layout: 'custom' })
</script>

非常に簡単だ。瞬間的に調べると、ここに到達することはできず、案外、最初に躓くポイントなのかと感じた。ただ、これだと、特定のレイアウトに切り替えたいページが複数ある場合に、この設定をすべてに書かねければならないため、保守の面でもあまり好ましくない。そのため、前述のmiddlewareで設定したファイル名をlayout.global.jsのように、拡張子前にglobalと入れれば、自動的にすべてのページで導入されるため、一箇所でレイアウトの設定を集約できる。

layoutをApp.vueで動的に設定

上記の内容は、今思えば非常にシンプルだが、レイアウトの設定は実はもう一つある。それは、App.vueでの設定だ。

<script setup>
const layout = 'custom';
</script>

<template>
  <div>
    <NuxtLayout :name="layout">
      <NuxtPage />
    </NuxtLayout>
  </div>
</template>

レイアウトを使用する際は、App.vueにて、NuxtLayoutコンポーネントを使用する。これのプロパティには、nameがあり、ここレイアウト名を設定すると、これが使用できる。これを利用して、パス名に応じた、レイアウトの切り替えを行っていた。

<script setup>
let layout = ref('custom');
const route = useRoute();
watch(
  () => route.path,
  (path) => {
    layout.value = route.path === '/custom' ? 'custom' : 'default';
  }
);
</script>

<template>
  <div>
    <NuxtLayout :name="layout">
      <NuxtPage />
    </NuxtLayout>
  </div>
</template>

App.vueにて、routeのパスを常に監視する。値の変更があれば、そのパスに合わせてレイアウト名を変更してあげれば、お手軽にページによるレイアウトファイルの切り替えが完成する。
これを用いていた理由は、/user/adminのような、対をなすパスだったため、この様にレイアウトの切り替えを行っていたが、この記述方法の影響で、この後頭を悩ませることになった。

layoutが設定できない

前述の通り、大きい枠組みで、とあるディレクトリ配下すべてをそれぞれ異なるレイアウトファイルに設定するようにApp.vueを編集した。ところが、特定の一つのページで新たなレイアウトに変更する必要が出てきた。一箇所だけなので、当然、この様に行う。

<script setup>
definePageMeta({ layout: 'special' })
</script>

腹ただしいことに、これが、なぜかいきなり、動作しなくなった。いくら調べても、再起動しても、特定のページで、異なるレイアウトを設定するのができなくなった。
犯人は、先程書いたApp.vueの設定だ。それぞれのページで設定したとしても、App.vueの設定がそれを上書きする。実行順序の問題だ。ただ、このままどちらかを諦めるということはできないため、最終的に取った手段は、これだ。

watch(
  () => route.path,
  (path) => {
    layout.value = route.path === '/custom' ? 'custom' : undefined;
  }
);

先程のApp.vueを少し書き換えた。三項演算子でのレイアウト名の代入において、'default'を直接記述せずに、undefinedとした。未定義だと、自動的にdefaultとなるため、問題なく、undefinedであれば、ページ単位での設定が上書きされない。なかなか同じ現象に遭遇した人を見つけられなかったので、少々時間を要した。

結論

middlewareで設定したほうが絶対にいい。
少なくとも、それぞれのファイルにレイアウト変更の記述が必要となるため、middlewareでその設定を押し込んだほうが、健全だろう。ただ、状況によっては、このような手段が必要になる人もいるかも知れないので、記録を残しておく。

Discussion