コンポーネントベースのVue.jsは、多くのWebアプリケーション開発において優れた選択肢です。 しかし、プロジェクトが大規模化し、純粋なSPAとして構築されたアプリケーションは、パフォーマンスやSEOの観点で新たな課題に直面するケースが少なくありません。
これらの課題に対する有力な解決策が、Vue.jsベースのプロダクションレベルフレームワークであるNuxtの導入です。 NuxtはSSRをはじめとする高度な機能を提供し、アプリケーションの品質を一段引き上げます。 しかし、その採用は既存のVueアプリケーションの設計思想からの転換を伴うため、移行には体系的な理解と戦略が不可欠です。 本稿は、Vue.jsからNuxtへの移行をテーマとした連載記事の第一弾です。 初回は、移行プロセスにおいて最も重要かつ根本的な変更点である「ルーティング」に焦点を当て、その技術的な差異と具体的な移行戦略を詳解します。
ルーティングは、URLに応じて描画すべきコンポーネントを決定する、アプリケーションのナビゲーションにおける中核機能です。 VueとNuxtでは、このルーティングの定義・管理のアプローチが根本的に異なります。
基本的なルーティング
Vueでのアプローチ
vue-router
では、routes
配列にルートオブジェクトを定義してルーティングを設定します。 各ルートには通常、パス、コンポーネント、そして任意で名前を指定します。
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
}
]
パスの一部を動的セグメント(パラメータ)にするには、コロン (:
) を使用します。
{
path: '/users/:id',
component: UserProfile
}
ネストしたルートは children
オプションで表現します。 親ルートのコンポーネントには、子のコンポーネントを描画するための <router-view>
が必要です。
{
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 />
を含める必要があります。
<template>
<div>
<!-- 親ページ共通のUI -->
<h2>設定</h2>
<nav>...</nav>
<hr>
<!-- 子ページがここに描画される -->
<NuxtPage />
</div>
</template>
pages/settings/index.vue
のような子ページのコンポーネントが、親の<NuxtPage />
の部分に描画されます。
<template>
<section>
<h3>プロフィール設定</h3>
<!-- ... -->
</section>
</template>
共通レイアウト
Vueでのアプローチ
複数のレイアウトを使い分けるには、手動での実装が必要です。 一般的なアプローチとして、vue-router
のルート定義にmeta
フィールドを活用する方法があります。
まず、各ルートにどのレイアウトを使用するかをmeta
情報として追加します。
const routes = [
{
path: '/',
component: Home,
meta: {layout: 'DefaultLayout' }
},
{
path: '/login',
component: Login,
meta: { layout: 'AuthLayout' }
}
]
次に、App.vue
側で、現在のルート情報のmeta
フィールドを監視する算出プロパティを用意し、動的にレイアウトコンポーネントを切り替えます。
<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 />
コンポーネントを置いた場所に、現在のページが描画されます。
<template>
<div>
<Header />
<main>
<slot />
</main>
<Footer />
</div>
</template>
特定のページに別のレイアウトを適用したい場合は、definePageMeta
コンパイラマクロを使用します。 例えば、layouts/auth.vue
というレイアウトを作成し、ログインページにのみ適用することができます。
<template>
<div>
<h1>Authentication</h1>
<slot />
</div>
</template>
そして、レイアウトを適用したいページコンポーネント内で、definePageMeta
を呼び出します。
<script setup>
definePageMeta({
layout: 'auth'
})
</script>
<template>
<div>
<!-- ログインフォーム -->
</div>
</template>
このように、Nuxtではファイルとディレクトリの規約に従うことで、Vueで必要だった手動のレイアウト切り替え処理を記述することなく、宣言的にレイアウトを管理できます。
ナビゲーションガード
Vueでのアプローチ
vue-router
では、グローバル、ルートごと、コンポーネント内の3つのレベルでガードを登録できます。
グローバルガード
ルーターインスタンスに直接登録し、全ルートに適用します。
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
return '/login'
}
})
ルートごとのガード
ルート定義のbeforeEnter
プロパティで、特定のルートにのみ適用します。
const routes = [
{
path: '/admin',
component: AdminPanel,
beforeEnter: (to, from) => {
if (!isAdmin()) return '/'
}
}
]
コンポーネント内ガード
コンポーネント内では、onBeforeRouteLeave
などを用いてナビゲーションを制御します。
<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
マクロを使って指定します。
export default defineNuxtRouteMiddleware((to, from) => {
if (!isAuthenticated()) {
return navigateTo('/login')
}
})
<script setup>
definePageMeta({
middleware: 'auth'
})
</script>
グローバルミドルウェア
ファイル名に.global
接尾辞を付けると、そのミドルウェアはアプリケーションのすべてのルートで自動的に実行されます。
export default defineNuxtRouteMiddleware((to, from) => {
console.log(`[Logger] Navigating from ${from.path} to ${to.path}`)
})
インラインミドルウェア
ページ固有のガードを、definePageMeta
内に直接匿名関数として定義することも可能です。 これは、そのページでしか使わない単純なガードに便利です。
<script setup>
definePageMeta({
middleware: [
(to, from) => {
console.log('Welcome to the promo page!')
}
]
})
</script>
リンクとプログラム遷移
Vueでのアプローチ
vue-router
では、<router-link>
コンポーネントとuseRouter
コンポーザブルがナビゲーションの基本的な手段となります。
宣言的ナビゲーション
テンプレート内でページ間をリンクするには、<router-link>
コンポーネントを使用します。 これは最終的に<a>
タグとしてレンダリングされ、to
プロパティで遷移先のルートを指定することもできます。
<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
が最も一般的に使われます。
<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.push
はnavigateTo
ヘルパー関数に置き換えられ、それぞれがNuxtの機能と深く統合されています。
宣言的ナビゲーション
<NuxtLink>
は<router-link>
とほぼ同じように使えますが、ビューポートに入った時にリンク先のページをプリフェッチするなど、パフォーマンスを向上させるための機能が組み込まれています。
<template>
<NuxtLink to="/">Home</NuxtLink>
<NuxtLink :to="{ name: 'user-profile', params: { id: 123 } }">
User Profile
</NuxtLink>
</template>
プログラムによるナビゲーション
Nuxtでは、navigateTo()
ヘルパー関数が用意されており、コンポーネント内やプラグインなど、どこからでも利用できます。この関数は自動的にインポートされます。
<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弾データフェッチ編はこちら!
この記事は役に立ちましたか?
もし参考になりましたら、下記のボタンで教えてください。
コメント