Nuxt.js でネストされたルートを使う

Nuxt.js
公開日:

Nuxt.js にはレイアウト機能がありますが、layouts 内の Vue テンプレートでは asyncData() を使うことができず、payload を渡したりするのに不便です。

そこで、レイアウトの代わりにネストされたルートを使うと、この問題を解決することができます。

ネストされたルートを使う

pages フォルダ内で、フォルダと同一名の Vue ファイルを設置することで使うことができます。

ここでは、/blog/ 以下のページで、カテゴリ一覧などのサイドバーを表示しておく親と、各記事を表示する子テンプレートを作ることを考えてみます。

pages/
|-- blog/
|   |-- _postId/
|       |-- index.vue
|-- blog.vue
|-- index.vue

このような木構造のとき、pages 直下の blog.vue は親テンプレートとなります。/blog/ 以下のページにいる間はずっと表示されますが、データの取得などは初めて表示されるときだけ行なわれます。

親テンプレートは、<nuxt-child> コンポーネントをテンプレート内に入れておく必要があります。このコンポーネントがないと、子ページの内容が表示されません。

<!-- pages/blog.vue(親テンプレート) -->
<template>
  <div class="columns">
    <div class="column">
      <nuxt-child />
    </div>
    <aside class="column is-one-quarter">
      <div class="menu">
        <p class="menu-label">カテゴリ</p>
        <ul class="menu-list mb-5">
          <li v-for="categ in categs" :key="`aside-categ-${categ.id}`">
            <nuxt-link :to="`/categories/${categ.id}/`" active-class="is-active">{{ categ.name }}</nuxt-link>
          </li>
        </ul>
      </div>
    </aside>
  </div>
</template>
<script>
export default {
  asyncData() {
    // カテゴリ情報を取得する処理
    return { categs }
  },
}
</script>

子テンプレートでは、普通に記事の内容を表示します。親の <nuxt-child> コンポーネントの位置に表示されます。

<!-- pages/blog/_postId/index.vue(子テンプレート) -->
<template>
  <article>
    <h1>{{ post.title }}</h1>
    <div class="content" v-html="post.content" />
  </article>
</template>
<script>
export default {
  asyncData({ params }) {
    // 記事情報を取得する処理
    return { post }
  },
}
</script>

親から子にデータを渡す

カテゴリアーカイブページを作るときなど、親が持っているデータを子で利用したい場面があります。

そのような時には、普通の Vue コンポーネントと同じように props を通じてデータを送ることができます。

<!-- pages/blog/categories/_categId/index.vue(子テンプレート) -->
<script>
export default {
  props: {
    // URL から categId は分かるが、カテゴリ名は分からない……
    categName: {
      type: String,
      default: '',
    },
  },
}
</script>

親では、<nuxt-child> コンポーネントに属性を付けてデータを送ります。

<!-- pages/blog.vue(親テンプレート)-->
<template>
  <nuxt-child categ-name="categName" />
</template>
<script>
export default {
  asyncData() {
    // カテゴリ情報を取得する処理
    return { categs }
  },
  computed: {
    categName() {
      const categId = this.$route.params.categId
      const categ = categId && this.categs.find(categ => categ.id === categId)
      return categ
        ? categ.namenull // 関係ないルート(記事詳細ページなど)で props を受け取らなくて済むように null を送る
    },
  },
}
</script>

子から親にデータを渡す

こちらも普通の Vue コンポーネントと同じく、イベントによってデータを渡せます。

子で this.$emit('eventname', dataIfNeeded) して、親で <nuxt-child @eventname="listener"> と書くだけです。

子のページトランジションがうまく行かない

親の <nuxt-child>:key="$route.path" を追加するとうまく行くかも知れません。

payload を渡す

npx nuxt generate で静的サイト生成する際、ネストされたルートが使われているページでは、親と子それぞれに payload が渡ります。

payload オブジェクトの中に parent, child キーなどで別々にオブジェクトを作ると、payload の受け取りが楽です。

// nuxt.config.js
// categs にカテゴリ情報、post に記事情報が入ってるとして
export default {
  generate: {
    routes: [
      {
        route: '/blog/example-post',
        payload: {
          parent: { categs },
          child: { post },
        },
      },
    ],
  },
}
<!-- pages/blog.vue(親テンプレート) -->
<script>
export default {
  asyncData({ payload, error }) {
    if (payload) return payload.parent
    error({ statusCode: 419, message: 'Page Expired' })
  },
}
</script>

<!-- pages/blog/_postId/index.vue(子テンプレート)-->
<script>
export default {
  asyncData({ payload, error }) {
    if (payload) return payload.child
    error({ statusCode: 419, message: 'Page Expired' })
  },
}
</script>

フロントページでネストされたルートを使いたい

pages フォルダ直下に index フォルダを作ります。

pages/
|-- index/
|   |-- index.vue
|-- index.vue

index ばかりで分かりにくいですが、pages/index.vue が親テンプレート、pages/index/index.vue が子テンプレートとなります。

他のページは index フォルダの中に作っていきます。