Vue Router
Client-seitiges Routing für Single-Page Applications
Was ist Vue Router?
Vue Router ist die offizielle Routing-Bibliothek für Vue.js. Sie ermöglicht es, URLs auf Komponenten zu mappen — ohne dass der Browser die Seite neu laden muss. Das Ergebnis: eine Single-Page Application (SPA), die sich wie eine native App anfühlt.
Vue Router unterstützt:
- Verschachtelte Routen (Nested Routes)
- Dynamische Segmente und Query-Parameter
- Navigation Guards (Middleware)
- Lazy Loading von Routen-Komponenten
- History- und Hash-Modus
- Scroll-Verhalten und Transitions
Nuxt und Vue Router
In Nuxt wird Vue Router automatisch konfiguriert — die Routen werden aus der Dateistruktur in pages/ generiert. Du musst Vue Router nicht manuell einrichten. Diese Lektion erklärt die Grundlagen für den Fall, dass du Vue ohne Nuxt verwendest oder das Routing tiefer verstehen willst. Im Nuxt-Modul zeigen wir, wie file-based Routing funktioniert.
Rails-Vergleich: routes.rb vs. Router-Config
In Rails definierst du Routen in config/routes.rb mit resources, get, post etc. Vue Router funktioniert ähnlich — du definierst Pfade und mappst sie auf Komponenten statt auf Controller-Actions. Der große Unterschied: Vue Router arbeitet clientseitig. Kein Server-Request pro Navigation.
Routen erstellen
Die Router-Instanz wird in main.ts erstellt und an die Vue-App übergeben. Jede Route verbindet einen Pfad mit einer Komponente.
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import UserList from '@/views/UserList.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/about',
name: 'about',
component: About,
},
{
path: '/users',
name: 'users',
component: UserList,
},
],
})
export default routerimport { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router) // Router als Plugin registrieren
app.mount('#app')Rails-Vergleich
createRouter() entspricht der Rolle von config/routes.rb. Der path ist die URL, die component ist der „Controller" (genauer: die View), und name ist wie ein Named Route Helper (users_path).
RouterLink und RouterView
<RouterView> ist der Platzhalter, wo die aktive Routen-Komponente gerendert wird. <RouterLink> erzeugt Links, die ohne Page-Reload navigieren.
<template>
<nav>
<!-- RouterLink erzeugt <a>-Tags mit SPA-Navigation -->
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">Über uns</RouterLink>
<RouterLink :to="{ name: 'users' }">Benutzer</RouterLink>
</nav>
<!-- Hier wird die aktive Routen-Komponente gerendert -->
<main>
<RouterView />
</main>
</template>
<style>
/* Aktiver Link automatisch gestylt */
.router-link-active {
color: #42b883;
font-weight: bold;
}
</style>Tipp: Active Class
RouterLink fügt automatisch die CSS-Klasse router-link-active hinzu, wenn der Link aktiv ist. Mit router-link-exact-active wird nur die exakte Route markiert. Das ist ideal für Navigationsmenüs.
Route Params und Query
Dynamische Segmente beginnen mit : und sind über route.params zugänglich. Query-Parameter stehen in route.query.
const routes = [
// Dynamisches Segment :id
{
path: '/users/:id',
name: 'user-profile',
component: () => import('@/views/UserProfile.vue'),
},
// Optionaler Parameter mit ?
{
path: '/posts/:slug?',
name: 'post',
component: () => import('@/views/Post.vue'),
},
// Catch-all Route (404)
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/NotFound.vue'),
},
]<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
// Reaktive Params und Query
const userId = computed(() => route.params.id)
const page = computed(() => route.query.page || '1')
// URL: /users/42?page=2
// → userId.value = '42'
// → page.value = '2'
</script>
<template>
<h1>Profil von Benutzer {{ userId }}</h1>
<p>Seite: {{ page }}</p>
</template>Rails-Vergleich
:id in Vue Router entspricht :id in resources :users. Statt params[:id] in Rails nutzt du route.params.id in Vue. Query-Parameter (?page=2) funktionieren genauso: params[:page] → route.query.page.
Verschachtelte Routen (Nested Routes)
Mit children definierst du Routen, die innerhalb einer Eltern-Komponente gerendert werden — perfekt für Layouts mit Sidebar oder Tab-Navigation.
const routes = [
{
path: '/users/:id',
component: () => import('@/views/UserLayout.vue'),
children: [
{
path: '', // /users/:id
name: 'user-overview',
component: () => import('@/views/UserOverview.vue'),
},
{
path: 'posts', // /users/:id/posts
name: 'user-posts',
component: () => import('@/views/UserPosts.vue'),
},
{
path: 'settings', // /users/:id/settings
name: 'user-settings',
component: () => import('@/views/UserSettings.vue'),
},
],
},
]<script setup lang="ts">
// UserLayout.vue — Eltern-Komponente mit eigenem <RouterView>
import { useRoute } from 'vue-router'
const route = useRoute()
const userId = computed(() => route.params.id)
</script>
<template>
<div class="user-layout">
<header>
<h1>Benutzer {{ userId }}</h1>
<nav>
<RouterLink :to="{ name: 'user-overview' }">Übersicht</RouterLink>
<RouterLink :to="{ name: 'user-posts' }">Beiträge</RouterLink>
<RouterLink :to="{ name: 'user-settings' }">Einstellungen</RouterLink>
</nav>
</header>
<!-- Hier werden die Kind-Routen gerendert -->
<RouterView />
</div>
</template>Navigation Guards
Guards sind Middleware-Funktionen, die vor (oder nach) einer Navigation ausgeführt werden. Sie sind perfekt für Authentifizierung, Berechtigungen oder das Speichern ungesicherter Änderungen.
Globaler Guard: beforeEach
// Globaler Guard — wird bei JEDER Navigation ausgeführt
router.beforeEach((to, from) => {
const auth = useAuthStore()
// Route erfordert Authentifizierung?
if (to.meta.requiresAuth && !auth.isLoggedIn) {
// Zur Login-Seite umleiten, Ziel-URL merken
return {
name: 'login',
query: { redirect: to.fullPath },
}
}
// Navigation erlauben
return true
})Komponentenbezogener Guard: beforeRouteEnter
<script setup lang="ts">
// In der Composition API nutzt du onBeforeRouteLeave / onBeforeRouteUpdate
import { onBeforeRouteLeave } from 'vue-router'
const hasUnsavedChanges = ref(false)
// Warnung beim Verlassen, wenn ungespeicherte Änderungen existieren
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges.value) {
const answer = window.confirm(
'Du hast ungespeicherte Änderungen. Wirklich verlassen?'
)
if (!answer) return false // Navigation abbrechen
}
})
</script>Rails-Vergleich: before_action
Guards entsprechen before_action Filtern in Rails-Controllern. beforeEach ist wie ein before_action im ApplicationController, beforeRouteEnter wie ein before_action in einem einzelnen Controller. return false entspricht redirect_to oder head :forbidden.
Programmatische Navigation
Statt <RouterLink> kannst du auch per JavaScript navigieren — zum Beispiel nach einem erfolgreichen Formular-Submit.
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
async function handleLogin() {
await loginUser(credentials)
// Nach erfolgreichem Login navigieren
router.push({ name: 'dashboard' })
// Oder mit Params
router.push({ name: 'user-profile', params: { id: '42' } })
// Oder mit Query
router.push({ path: '/search', query: { q: 'vue' } })
// Ersetzen statt Push (kein neuer History-Eintrag)
router.replace({ name: 'home' })
// Vor/Zurück navigieren
router.go(-1) // Zurück
router.go(1) // Vorwärts
}
</script>
<template>
<form @submit.prevent="handleLogin">
<!-- Formular -->
<button type="submit">Einloggen</button>
</form>
</template>Route Meta Fields
Mit meta kannst du beliebige Daten an Routen anhängen — zum Beispiel ob eine Route Authentifizierung erfordert oder welchen Seitentitel sie haben soll.
const routes = [
{
path: '/dashboard',
component: Dashboard,
meta: {
requiresAuth: true,
title: 'Dashboard',
role: 'admin',
},
},
{
path: '/public',
component: PublicPage,
meta: {
requiresAuth: false,
title: 'Öffentliche Seite',
},
},
]
// In einem Guard auf Meta zugreifen
router.beforeEach((to) => {
// Seitentitel setzen
document.title = (to.meta.title as string) || 'Meine App'
// Rollenprüfung
if (to.meta.role && !userHasRole(to.meta.role as string)) {
return { name: 'forbidden' }
}
})Lazy Loading von Routen
Standardmäßig werden alle Routen-Komponenten in ein einziges Bundle gepackt. Mit Lazy Loading wird jede Route erst geladen, wenn sie tatsächlich besucht wird — das reduziert die initiale Bundle-Größe erheblich.
const routes = [
{
path: '/',
name: 'home',
// Wird sofort geladen (im Haupt-Bundle)
component: Home,
},
{
path: '/admin',
name: 'admin',
// Lazy Loading — eigenes Chunk, erst bei Besuch geladen
component: () => import('@/views/AdminDashboard.vue'),
},
{
path: '/reports',
name: 'reports',
// Lazy Loading mit explizitem Chunk-Name (Vite)
component: () => import('@/views/Reports.vue'),
},
]
// Bundle-Analyse vorher:
// main.js — 500 KB (alles drin)
//
// Bundle-Analyse nachher:
// main.js — 200 KB (nur Home + Router)
// admin.js — 150 KB (bei Bedarf geladen)
// reports.js — 150 KB (bei Bedarf geladen)Tipp: Named Chunks
Der Kommentar /* webpackChunkName: "admin" */ (Webpack) bzw. der Rollup-basierte Splitting in Vite erzeugt separate JS-Dateien pro Route. In Nuxt passiert das automatisch für alle Seiten in pages/.
Live-Demo: Router-Simulation
Diese Demo simuliert ein einfaches Routing-System. Klicke auf die Links, um zwischen „Seiten" zu wechseln — alles passiert clientseitig.
🏠 Startseite
Willkommen! Dies ist die Home-Route (/).
💡 In einer echten App würde <RouterView> die Komponente rendern und die Browser-URL sich ändern.
Zusammenfassung
// Router erstellen
const router = createRouter({ history: createWebHistory(), routes })
// Navigation
router.push('/path') // Navigieren
router.push({ name: 'route', params }) // Named Route
router.replace('/path') // Ohne History-Eintrag
router.go(-1) // Zurück
// In Komponenten
const route = useRoute() // Aktuelle Route lesen
const router = useRouter() // Router-Instanz
// Route-Daten
route.params.id // Dynamische Params
route.query.page // Query-Parameter
route.meta.requiresAuth // Meta-Felder
route.name // Route-Name
route.fullPath // Volle URL mit Query
// Guards
router.beforeEach((to, from) => { }) // Global
onBeforeRouteLeave((to, from) => { }) // Komponentenbezogen
onBeforeRouteUpdate((to, from) => { }) // Bei Param-ÄnderungDas Wichtigste
- createRouter definiert Pfad-zu-Komponente-Mappings.
- RouterView rendert die aktive Route, RouterLink navigiert.
- Dynamische Params (
:id) und Query (?page=2) überuseRoute(). - Nested Routes via
childrenfür Layouts mit Sub-Navigation. - Navigation Guards sind Vue's
before_action. - Lazy Loading mit
() => import()für kleinere Bundles. - In Nuxt ist das alles automatisch — file-based Routing in
pages/.