【Nuxt.js】動的ルート・ネストされたルートの書き方

JavaScript

こんにちは、かつコーチです。

前回はlayouts/を使った共通レイアウトの管理方法を解説しました。

今回は一歩進んで「/posts/1のようにIDが変わるページ」を作る動的ルートを扱います。

「ブログ記事の詳細ページってどう作るの」という悩みを、この記事で解決します。

動的ルートとは?

用語の定義

動的ルートとは、URLの一部が固定文字列ではなく、可変の値になっているルートのことです。

たとえば/posts/1/posts/2のように、記事IDだけが変わるページが該当します。

Nuxtでは、ファイル名を角括弧[ ]で囲むことで、この動的な部分を表現します。

なぜ必要なのか

もし動的ルートがなければ、記事の数だけpages/posts/1.vuepages/posts/2.vue…とファイルを作る必要があります。

これは記事が増えるたびにファイルを追加する、非現実的な運用です。

動的ルートを使えば、1つのファイルで無限のパターンのURLに対応できます。

基本の書き方

手順1:[id].vueで動的セグメントを作る

pages/posts/[id].vueというファイルを作成します。

<!-- pages/posts/[id].vue -->
<script setup lang="ts">
const route = useRoute()
const postId: string = route.params.id as string
</script>

<template>
  <div>
    <h2>記事ID: {{ postId }}</h2>
  </div>
</template>

/posts/1にアクセスするとroute.params.id"1"が入り、/posts/99なら"99"が入ります。

手順2:useRouteでパラメータを取得する

パラメータの取得にはuseRoute()というコンポーザブル(再利用可能な処理をまとめた関数)を使います。

<script setup lang="ts">
interface Post {
  id: number
  title: string
}

const route = useRoute()
const postId = Number(route.params.id)

const { data: post } = await useFetch<Post>(`/api/posts/${postId}`)
</script>

<template>
  <div v-if="post">
    <h2>{{ post.title }}</h2>
  </div>
</template>

取得したIDをAPIのパスに埋め込むことで、記事ごとの詳細データを取得できます。

つまずきやすい設定・注意点:paramsは常に文字列

私が実際にハマったのは、route.params.idの型です。

URLパラメータは常にstring型で渡ってくるため、数値として比較しようとすると失敗します。

const route = useRoute()

// ❌ これは動かない("1" !== 1 のため常にfalse)
if (route.params.id === 1) {
  console.log("1番目の記事です")
}

数値として扱いたい場合はNumber()で明示的に変換する必要があります。

const route = useRoute()
const id = Number(route.params.id)

if (id === 1) {
  console.log("1番目の記事です")
}

このミスに気づかず「if文の中に絶対入るはずなのに入らない」と、しばらく原因を探した経験があります。

よくあるつまずきポイント・エラー対処

複数の動的セグメントを使うとき

記事のコメント詳細のように、複数のIDが必要なページもあります。

❌ Before:1つのファイルに無理やり詰め込む

pages/posts/[id]-comment-[commentId].vue

Nuxtのファイルベースルーティングでは、このような書き方はエラーになります。

実行すると[nuxt] Invalid routeのようなビルドエラーが表示されました。

✅ After:ネストしたディレクトリ構成にする

pages/
  posts/
    [id]/
      comments/
        [commentId].vue
<!-- pages/posts/[id]/comments/[commentId].vue -->
<script setup lang="ts">
const route = useRoute()
const postId = route.params.id as string
const commentId = route.params.commentId as string
</script>

<template>
  <p>記事{{ postId }}のコメント{{ commentId }}</p>
</template>

ディレクトリを分けることで、URLは/posts/1/comments/5のように対応します。

複数の動的な値を扱うときは、1ファイルに詰め込まず、ディレクトリ階層で表現しましょう。

応用・一歩先の使い方

ネストされたルートと<NuxtPage/>

一覧ページの中に詳細ページを表示したい場合は、ネストされたルートを使います。

親ページと同名のディレクトリを作り、その中に子ページを置く構成です。

pages/
  posts.vue
  posts/
    [id].vue
<!-- pages/posts.vue(親) -->
<template>
  <div>
    <h1>記事一覧</h1>
    <NuxtPage />
  </div>
</template>

親のposts.vue内に<NuxtPage />を置くことで、/posts/1にアクセスしたときに[id].vueの中身がここへ差し込まれます。

一覧のサイドバーを固定したまま、右側だけ詳細内容を切り替える、といったUIが実現できます。

catch-allルート([...slug].vue)

「それ以外の全てのパス」を1つのページで受け取りたい場合は、[...slug].vueという書き方も使えます。

<!-- pages/[...slug].vue -->
<script setup lang="ts">
const route = useRoute()
const slug = route.params.slug as string[]
</script>

<template>
  <p>404: {{ slug.join('/') }} は見つかりませんでした</p>
</template>

存在しないURLの受け皿(404ページ)や、階層が可変なドキュメントページなどで活躍します。

まとめ

この記事のポイント

  • [id].vueのようにファイル名を角括弧で囲むと動的ルートになる
  • route.paramsで取得できる値は常にstring型なので、数値比較にはNumber()変換が必要
  • 動的な値が複数あるときは、1ファイルに詰め込まずディレクトリ階層で表現する
  • 親ページに<NuxtPage />を置くとネストされたルートが構成できる
  • [...slug].vueで任意階層のパスをまとめて受け取れる

次に読むべき記事

動的ルートが扱えるようになったら、次はページ遷移を制御するミドルウェアを学んでいきましょう。

→ 次の記事:ルートミドルウェアでページ遷移を制御する

タグ: Nuxt.js, 中級者向け, ルーティング

タイトルとURLをコピーしました