VueからNuxtへの移行ガイド 第1弾【ルーティング編】

コンポーネントベースのVue.jsは、多くのWebアプリケーション開発において優れた選択肢です。 しかし、プロジェクトが大規模化し、純粋なSPAとして構築されたアプリケーションは、パフォーマンスやSEOの観点で新たな課題に直面するケースが少なくありません。
これらの課題に対する有力な解決策が、Vue.jsベースのプロダクションレベルフレームワークであるNuxtの導入です。 NuxtはSSRをはじめとする高度な機能を提供し、アプリケーションの品質を一段引き上げます。 しかし、その採用は既存のVueアプリケーションの設計思想からの転換を伴うため、移行には体系的な理解と戦略が不可欠です。 本稿は、Vue.jsからNuxtへの移行をテーマとした連載記事の第一弾です。 初回は、移行プロセスにおいて最も重要かつ根本的な変更点である「ルーティング」に焦点を当て、その技術的な差異と具体的な移行戦略を詳解します。

目次を読み込み中…

ルーティングは、URLに応じて描画すべきコンポーネントを決定する、アプリケーションのナビゲーションにおける中核機能です。 VueとNuxtでは、このルーティングの定義・管理のアプローチが根本的に異なります。

基本的なルーティング

Vueでのアプローチ

vue-routerでは、routes配列にルートオブジェクトを定義してルーティングを設定します。 各ルートには通常、パス、コンポーネント、そして任意で名前を指定します。

js
const routes = [
    {
        path: '/',
        name: 'home',
        component: Home
    },
    {
        path: '/about',
        name: 'about',
        component: About
    }
]

パスの一部を動的セグメント(パラメータ)にするには、コロン (:) を使用します。

vue
{
    path: '/users/:id',
    component: UserProfile
}

ネストしたルートは children オプションで表現します。 親ルートのコンポーネントには、子のコンポーネントを描画するための <router-view> が必要です。

vue
{
    path: '/settings',
    component: SettingsLayout,
    children: [
        {
            path: '',
            component: SettingsProfile
        }
    ]
}

Nuxtでのアプローチ

Nuxtでは、pagesディレクトリのファイル構造がルートに変換されます。

pages/
├── index.vue   // path: '/'
└── about.vue   // path: '/about'

動的なルートは、ファイル名を角括弧で囲んで作成します。

pages/
└── users/
    └── [id].vue  // path: /users/:id

ネストしたルートは、親コンポーネントとなるファイル(settings.vue)と、子ルートのディレクトリ(settings/)で表現します。 親コンポーネントには、子ページを描画するための<NuxtPage />を含める必要があります。

vue
<template>
    <div>
        <!-- 親ページ共通のUI -->
        <h2>設定</h2>
        <nav>...</nav>
        <hr>

        <!-- 子ページがここに描画される -->
        <NuxtPage />
    </div>
</template>

pages/settings/index.vueのような子ページのコンポーネントが、親の<NuxtPage />の部分に描画されます。

vue
<template>
    <section>
        <h3>プロフィール設定</h3>
        <!-- ... -->
    </section>
</template>

共通レイアウト

Vueでのアプローチ

複数のレイアウトを使い分けるには、手動での実装が必要です。 一般的なアプローチとして、vue-routerのルート定義にmetaフィールドを活用する方法があります。

まず、各ルートにどのレイアウトを使用するかをmeta情報として追加します。

vue
const routes = [
    {
        path: '/',
        component: Home,
        meta: {layout: 'DefaultLayout' }
    },
    {
        path: '/login',
        component: Login,
        meta: { layout: 'AuthLayout' }
    }
]

次に、App.vue側で、現在のルート情報のmetaフィールドを監視する算出プロパティを用意し、動的にレイアウトコンポーネントを切り替えます。

vue
<script setup>
const layouts = {
  DefaultLayout,
  AuthLayout
}

const route = useRoute()
// ルートのmeta情報に応じてレイアウトを決定し、指定がなければデフォルトのレイアウトを使う
const layout = computed(
  () => layouts[route.meta.layout] || DefaultLayout
)
</script>

<template>
  <component :is="layout">
    <router-view />
  </component>
</template>

この方法により、ルートに応じて柔軟にレイアウトを適用できますが、レイアウトの登録や動的な切り替え処理をアプリケーション側で管理する必要があります。

Nuxtでのアプローチ

Nuxtでは、layouts/ディレクトリを使った規約ベースでレイアウトを管理します。 このディレクトリに作成されたVueコンポーネントは、自動的にレイアウトとして認識されます。 layouts/default.vueというファイル名のレイアウトは、アプリケーション全体のデフォルトとして適用されます。 レイアウトコンポーネント内では、<slot />コンポーネントを置いた場所に、現在のページが描画されます。

vue
<template>
  <div>
    <Header />
    <main>
      <slot />
    </main>
    <Footer />
  </div>
</template>

特定のページに別のレイアウトを適用したい場合は、definePageMetaコンパイラマクロを使用します。 例えば、layouts/auth.vueというレイアウトを作成し、ログインページにのみ適用することができます。

vue
<template>
  <div>
    <h1>Authentication</h1>
    <slot />
  </div>
</template>

そして、レイアウトを適用したいページコンポーネント内で、definePageMetaを呼び出します。

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

<template>
  <div>
    <!-- ログインフォーム -->
  </div>
</template>

このように、Nuxtではファイルとディレクトリの規約に従うことで、Vueで必要だった手動のレイアウト切り替え処理を記述することなく、宣言的にレイアウトを管理できます。

ナビゲーションガード

Vueでのアプローチ

vue-routerでは、グローバル、ルートごと、コンポーネント内の3つのレベルでガードを登録できます。

グローバルガード

ルーターインスタンスに直接登録し、全ルートに適用します。

vue
router.beforeEach((to, from) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    return '/login'
  }
})

ルートごとのガード

ルート定義のbeforeEnterプロパティで、特定のルートにのみ適用します。

vue
const routes = [
  {
    path: '/admin',
    component: AdminPanel,
    beforeEnter: (to, from) => {
      if (!isAdmin()) return '/'
    }
  }
]

コンポーネント内ガード

コンポーネント内では、onBeforeRouteLeaveなどを用いてナビゲーションを制御します。

vue
<script setup>
import { onBeforeRouteLeave } from 'vue-router'

onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('変更が保存されていません。離れますか?')
  if (!answer) return false
})
</script>

Nuxtでのアプローチ

Nuxtでは、「ルートミドルウェア」を用いてナビゲーションを制御します。これはvue-routerのナビゲーションガードに相当し、middlewareディレクトリで管理されます。

名前付きミドルウェア

middleware/ディレクトリに作成したファイルは、ファイル名がそのままミドルウェア名になります。 作成したミドルウェアは、各ページコンポーネントでdefinePageMetaマクロを使って指定します。

vue
export default defineNuxtRouteMiddleware((to, from) => {
  if (!isAuthenticated()) {
    return navigateTo('/login')
  }
})

<script setup>
definePageMeta({
  middleware: 'auth'
})
</script>

グローバルミドルウェア

ファイル名に.global接尾辞を付けると、そのミドルウェアはアプリケーションのすべてのルートで自動的に実行されます。

vue
export default defineNuxtRouteMiddleware((to, from) => {
  console.log(`[Logger] Navigating from ${from.path} to ${to.path}`)
})

インラインミドルウェア

ページ固有のガードを、definePageMeta内に直接匿名関数として定義することも可能です。 これは、そのページでしか使わない単純なガードに便利です。

vue
<script setup>
definePageMeta({
  middleware: [
    (to, from) => {
      console.log('Welcome to the promo page!')
    }
  ]
})
</script>

リンクとプログラム遷移

Vueでのアプローチ

vue-routerでは、<router-link>コンポーネントとuseRouterコンポーザブルがナビゲーションの基本的な手段となります。

宣言的ナビゲーション

テンプレート内でページ間をリンクするには、<router-link>コンポーネントを使用します。 これは最終的に<a>タグとしてレンダリングされ、toプロパティで遷移先のルートを指定することもできます。

vue
<template>
  <router-link to="/">Home</router-link>
  <router-link :to="{ name: 'user-profile', params: { id: 123 } }">
    User Profile
  </router-link>
</template>

プログラムによるナビゲーション

<script setup>内で、例えばボタンのクリック時などに画面遷移を制御するには、useRouterから取得したルーターインスタンスのメソッドを使用します。 router.pushが最も一般的に使われます。

vue
<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

function goToAboutPage() {
  router.push('/about')
}

function goToUserProfile(userId) {
  router.push({ name: 'user-profile', params: { id: userId } })
}
</script>

Nuxtでのアプローチ

Nuxtでは、<router-link><NuxtLink>に、router.pushnavigateToヘルパー関数に置き換えられ、それぞれがNuxtの機能と深く統合されています。

宣言的ナビゲーション

<NuxtLink><router-link>とほぼ同じように使えますが、ビューポートに入った時にリンク先のページをプリフェッチするなど、パフォーマンスを向上させるための機能が組み込まれています。

vue
<template>
  <NuxtLink to="/">Home</NuxtLink>
  <NuxtLink :to="{ name: 'user-profile', params: { id: 123 } }">
    User Profile
  </NuxtLink>
</template>

プログラムによるナビゲーション

Nuxtでは、navigateTo()ヘルパー関数が用意されており、コンポーネント内やプラグインなど、どこからでも利用できます。この関数は自動的にインポートされます。

vue
<script setup>
function goToAboutPage() {
  navigateTo('/about')
}

function goToUserProfile(userId) {
  navigateTo({ name: 'user-profile', params: { id: userId } })
}
</script>

移行戦略ガイド

VueからNuxtへの移行は、一つ一つの機能を丁寧に対応付けていくことで、スムーズに進めることができます。

ルーティングの移行

ファイル構造の再構築 移行の最初のステップは、pages/ディレクトリの構築です。既存のsrc/router/index.jsファイルを開き、routes配列の定義を元に対応するVueコンポーネントをpages/以下に配置します。

  • { path: '/about', ... } は pages/about.vue になります。
  • 動的なルート{ path: '/users/:id', ... } は pages/users/[id].vue で表現します。
  • ネストしたルートは、pages/settings.vue(親)とpages/settings/index.vue(子)のようなディレクトリ構造で再現します。

ルーター関連ファイルの整理 pages/ディレクトリが完成したら、Vue Routerの設定ファイル(src/router/index.js)と、main.jsなどで行っていたapp.use(router)の初期化処理は不要になるため削除します。

コンポーネントの更新 最後に、NuxtのAPIに合わせてコンポーネントを更新します。

  • ネストしたルートの親コンポーネントで使っていた<router-view />は、Nuxtの<NuxtPage />に置き換えます。
  • useRoute()はNuxtでは自動的にインポートされるため、import { useRoute } from 'vue-router' の行は削除します。

共通レイアウトの移行

VueのApp.vueなどで実装していた動的なレイアウト切り替えロジックは、Nuxtの規約ベースのシステムに移行します。具体的な手順は以下の通りです。

  • まず、レイアウトとして使っていたコンポーネントをlayouts/ディレクトリに移動します。
  • コンポーネント内でページを描画していた<router-view />を、<slot />に置き換えます。
  • App.vueに記述していたレイアウトを動的に決定するロジック(算出プロパティなど)はすべて削除します。
  • レイアウトの適用は、ページ側のdefinePageMetaマクロで行うように書き換えます。

ナビゲーションガードの移行

vue-routerのナビゲーションガードは、Nuxtのルートミドルウェアに移行します。

  • router.beforeEachで登録していたグローバルガードは、middleware/ディレクトリに.global.tsという接尾辞を付けたファイル(例: auth.global.ts)としてロジックを移します。
  • ルートごとのbeforeEnterガードは、名前付きミドルウェア(例: middleware/admin-only.ts)として切り出し、ルート側のdefinePageMetaで指定します。
  • リダイレクト処理は、return '/path'next('/path')から、NuxtのnavigateTo()ヘルパー関数に置き換えることを忘れないでください。

リンクとプログラム遷移の移行

このステップは、プロジェクト全体にわたる機械的な置換が中心となります。

  • テンプレート内のすべての<router-link>コンポーネントを、Nuxtの<NuxtLink>に置き換えます。APIはほぼ互換性があるため、単純な置換で問題ないケースがほとんどです。
  • <script>内でuseRouterを使って行っていたプログラムによる画面遷移(router.pushなど)を、Nuxtが提供するnavigateTo()ヘルパー関数に置き換えます。

まとめ

VueからNuxtへのルーティング移行は、単なるシンタックスの置き換えではなく、規約ベースの設計思想への転換です。本稿で解説した各機能の違いと移行戦略を理解することで、スムーズな移行が実現できるでしょう。

第2弾データフェッチ編はこちら!

この記事は役に立ちましたか?

もし参考になりましたら、下記のボタンで教えてください。

関連記事

コメント

この記事へのコメントはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)