<v/>
{ }
Vue Router · Nested Routes · Navigation Guards · SPA
Vue Router praktisch nutzen
Nested Routes, Guards und Scroll Behavior

Vue Router ist weit mehr als ein URL-Switcher. Wer Nested Routes, Navigation Guards und gezieltes Scroll-Verhalten sauber einsetzt, baut Single-Page-Applications, die sich wie echte Webanwendungen anfühlen – mit kontrolliertem Zugriff, sauberem Ladeverhalten und keiner einzigen unerwarteten Scrollposition.

15 Min. Lesezeit beforeEach · Nested Routes · Lazy Loading · scrollBehavior Vue 3 · Vue Router 4

1. Was Vue Router leistet – und was er nicht ist

Vue Router ist die offizielle Routing-Bibliothek für Vue.js und übernimmt die Aufgabe, URL-Pfade auf Komponenten abzubilden. In einer Single-Page-Application gibt es keinen echten Seitenaufruf mehr – der Browser lädt einmalig die Anwendung, und alle weiteren Navigationsereignisse werden vom Router abgefangen und clientseitig verarbeitet. Das ermöglicht schnelle Übergänge ohne Reload, erhält den Anwendungszustand und ermöglicht animierte Seitenübergänge, die mit klassischen Multi-Page-Apps nicht möglich wären.

Was Vue Router nicht ist: ein Zustandsmanager. Routingdaten wie aktuelle Params oder Query-String-Werte sind kurzlebige Navigationszustände, keine persistenten Applikationsdaten. Die Grenze zwischen Router-State und Store-State zu kennen, ist entscheidend für saubere Architektur. URL-Parameter, die die aktuelle Ansicht steuern, gehören in die Route. Benutzerdaten, API-Responses und UI-Zustände gehören in Pinia oder den Composable-State. Wer Vue Router nur als einfachen Link-Handler versteht und Nested Routes, Guards und Scroll Behavior ignoriert, verschenkt einen großen Teil der Bibliothek.

2. Router-Instanz und History-Modi richtig konfigurieren

Vue Router 4 unterstützt drei History-Modi: createWebHistory für saubere URLs ohne Hash (erfordert Server-Konfiguration, die alle Pfade auf index.html umleitet), createWebHashHistory für Hash-basierte URLs (kein Server-Setup nötig, aber SEO-Nachteile) und createMemoryHistory für serverseitiges Rendering. In Produktionsumgebungen ist createWebHistory die richtige Wahl, sofern der Webserver korrekt konfiguriert ist. Bei Nginx reicht ein simpler try_files $uri $uri/ /index.html;-Block in der Location-Konfiguration.

Die Router-Instanz wird in einer eigenen Datei src/router/index.ts erstellt und in der main.ts mit app.use(router) registriert. Das Trennen der Router-Konfiguration von der App-Instanz ermöglicht das Importieren des Routers in Composables und Stores, ohne zirkuläre Abhängigkeiten zu riskieren. Die Option scrollBehavior wird direkt bei der Erstellung übergeben – sie gehört in die Router-Instanz, nicht in einzelne Routen-Definitionen.


// src/router/index.ts — Router instance with history mode and base URL
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    component: () => import('@/layouts/DefaultLayout.vue'),
    children: [
      {
        path: '',
        name: 'home',
        component: () => import('@/views/HomeView.vue'),
        meta: { title: 'Startseite' }
      },
      {
        path: 'blog',
        name: 'blog',
        component: () => import('@/views/BlogView.vue'),
        meta: { title: 'Blog' }
      }
    ]
  },
  {
    path: '/admin',
    component: () => import('@/layouts/AdminLayout.vue'),
    meta: { requiresAuth: true, role: 'admin' },
    children: [
      {
        path: '',
        name: 'admin-dashboard',
        component: () => import('@/views/admin/DashboardView.vue')
      }
    ]
  }
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
  scrollBehavior(to, from, savedPosition) {
    // Restore position on browser back/forward
    if (savedPosition) return savedPosition
    // Scroll to anchor if present
    if (to.hash) return { el: to.hash, behavior: 'smooth' }
    // Default: top of page
    return { top: 0 }
  }
})

export default router

3. Nested Routes: Layouts mit verschachtelten router-view

Nested Routes sind das mächtigste Feature von Vue Router, das in vielen Projekten nicht vollständig genutzt wird. Das Konzept: Ein Layout-Komponent enthält ein <router-view />, in das die Child-Route gerendert wird. Das Layout selbst – Navigation, Sidebar, Footer – bleibt dabei bestehen, nur der innere Inhalt wechselt. So vermeidet man das Neu-Mounten von Komponenten, die bei jedem Routenwechsel identisch bleiben, und erhält den Zustand von Elementen wie Suchfeldern oder offenen Dropdowns.

Ein typisches Muster in komplexen Anwendungen sind drei Layout-Ebenen: ein globales Layout mit Top-Navigation, ein Bereichs-Layout (z. B. für den Admin-Bereich mit Sidebar) und die eigentliche View-Komponente. Jede Ebene hat ihr eigenes <router-view />. Die Routen-Konfiguration spiegelt diese Hierarchie direkt im children-Array wider. Named Views ermöglichen zusätzlich, mehrere <router-view /> auf derselben Ebene zu befüllen – z. B. ein Haupt-Content-Bereich und eine Sidebar, die je nach Route unterschiedliche Komponenten zeigen.

4. Lazy Loading: Routen auf Demand laden

Lazy Loading im Kontext von Vue Router bedeutet, dass der JavaScript-Code einer View-Komponente erst dann vom Server geladen wird, wenn die entsprechende Route zum ersten Mal aufgerufen wird. Ohne Lazy Loading landet der Code aller Views im initialen Bundle, was besonders bei größeren Anwendungen die Ladezeit der ersten Seite deutlich verlängert. Die Syntax ist denkbar einfach: Statt import HomeView from '@/views/HomeView.vue' schreibt man component: () => import('@/views/HomeView.vue'). Vite und Webpack erkennen dieses Dynamic-Import-Muster und erstellen automatisch separate Chunks.

Für bessere Kontrolle über die erzeugten Chunks empfiehlt sich die Verwendung von Magic Comments: () => import(/* webpackChunkName: "admin" */ '@/views/admin/DashboardView.vue'). Alle Routen mit demselben Chunk-Namen landen in einem gemeinsamen Bundle, was für Modulgruppen wie den Admin-Bereich sinnvoll ist. In Vite-Projekten funktioniert das Rollup-Äquivalent /* @vite-ignore */ für dynamische Pfade. Eine häufig übersehene Optimierung: Kritische Routen nach dem initialen Render im Hintergrund vorladen mit router.resolve({ name: 'checkout' }) kombiniert mit einem dynamischen Import.


// src/router/index.ts — Lazy loading with chunk grouping
const routes: RouteRecordRaw[] = [
  // Eager load: always in main bundle (critical path)
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  // Lazy load: separate chunk per route
  {
    path: '/produkte',
    name: 'products',
    component: () => import('@/views/ProductsView.vue')
  },
  // Grouped chunk: all admin views share one bundle
  {
    path: '/admin',
    component: () => import(/* webpackChunkName: "admin" */ '@/layouts/AdminLayout.vue'),
    children: [
      {
        path: 'users',
        component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/UsersView.vue')
      },
      {
        path: 'orders',
        component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/OrdersView.vue')
      }
    ]
  }
]

// Preload checkout after initial render (background)
router.isReady().then(() => {
  import('@/views/CheckoutView.vue')
})

Navigation Guards sind die Mechanismus, mit dem Vue Router Navigationen abfangen, prüfen und umleiten kann. Der globale Guard router.beforeEach wird bei jeder Navigation ausgeführt und eignet sich für übergreifende Logik wie Authentifizierungsprüfungen, Seitentitel-Updates und Analytics-Tracking. Der Guard erhält to (Zielroute), from (Quellroute) und gibt entweder true zurück (Navigation erlauben), false (abbrechen), einen Routen-Objekt (umleiten) oder nichts (erlauben, da undefined als true gilt).

Route-Level-Guards mit beforeEnter auf der Routen-Definition sind ideal für routen-spezifische Prüfungen, die nicht für jede Route relevant sind. In-Component-Guards wie onBeforeRouteLeave (Composition API) ermöglichen das Abfangen des Verlassens einer Komponente – typischer Anwendungsfall: ungespeicherte Formulardaten. Der Guard kann einen Bestätigungsdialog anzeigen und die Navigation je nach Nutzerantwort zulassen oder abbrechen. Wichtig: In-Component-Guards laufen nicht, wenn die Komponente neu gemountet wird, sondern nur bei Routenwechseln, bei denen die Komponente bereits aktiv ist.


// src/router/guards.ts — Auth guard using Pinia store
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

export async function authGuard(
  to: RouteLocationNormalized,
  from: RouteLocationNormalized,
  next: NavigationGuardNext
) {
  const auth = useAuthStore()

  // Initialize auth state from persisted token
  if (!auth.initialized) {
    await auth.initialize()
  }

  const requiresAuth = to.meta.requiresAuth as boolean | undefined
  const requiredRole = to.meta.role as string | undefined

  if (requiresAuth && !auth.isAuthenticated) {
    // Preserve intended destination for post-login redirect
    return next({ name: 'login', query: { redirect: to.fullPath } })
  }

  if (requiredRole && !auth.hasRole(requiredRole)) {
    return next({ name: 'forbidden' })
  }

  // Update document title from route meta
  if (to.meta.title) {
    document.title = `${to.meta.title} | Mironsoft`
  }

  return next()
}

// In-component guard: warn on unsaved changes
// Usage in <script setup>
import { onBeforeRouteLeave } from 'vue-router'

onBeforeRouteLeave((to, from, next) => {
  if (hasUnsavedChanges.value) {
    const confirmed = window.confirm('Änderungen verwerfen?')
    return next(confirmed)
  }
  next()
})

6. Route Meta-Felder für Auth und Rollen

Route Meta-Felder sind beliebige Daten, die man an eine Routen-Definition anhängen kann und die in Navigation Guards und Komponenten über route.meta verfügbar sind. Das klassische Anwendungsfeld ist die Deklaration von Zugriffsrechten direkt in der Routen-Konfiguration: meta: { requiresAuth: true, role: 'admin' }. Dieser deklarative Ansatz hält die Zugriffslogik zentral in der Router-Konfiguration und vermeidet duplizierte Prüfungen in einzelnen Komponenten. Der globale beforeEach-Guard liest dann die Meta-Felder aus und entscheidet über die Navigation.

Weitere sinnvolle Meta-Felder: title für den Seitentitel (im Guard für document.title auswerten), layout für dynamische Layout-Selektion, transition für routen-spezifische Übergangsanimationen und keepAlive für die Steuerung der <KeepAlive>-Komponente. TypeScript-Typisierung von Meta-Feldern erfolgt über Module Augmentation der RouteMeta-Schnittstelle aus vue-router. Ohne diese Typisierung sind alle Meta-Felder vom Typ unknown, was explizite Casts im Guard erforderlich macht.

7. Scroll Behavior: Kontrolle über die Scrollposition

Das Scroll Behavior in Vue Router ist die Option, die bestimmt, wohin der Browser nach einer Navigation scrollt. Ohne explizite Konfiguration erbt Vue Router das Standard-Browserverhalten, das in SPAs häufig falsch ist: Bei Back-Navigation behält der Browser die Scrollposition nicht, bei normaler Navigation scrollt er nicht zur Seitenoberseite. Die scrollBehavior-Funktion bekommt die Zielroute, die Quellroute und die gespeicherte Position (bei Browser-Back/Forward) übergeben.

Für Anker-Navigation ist das Muster if (to.hash) return { el: to.hash, behavior: 'smooth' } der richtige Weg. Vue Router findet das Element mit document.querySelector(to.hash) und scrollt dorthin. Bei Anwendungen mit Server-Side-Rendering oder asynchronen Komponenten kann die Ziel-DOM-Komponente zum Zeitpunkt des Scrollens noch nicht gerendert sein. In diesem Fall bietet sich eine kurze Verzögerung mit new Promise(resolve => setTimeout(resolve, 100)) oder das Zurückgeben eines Promises aus scrollBehavior an, das aufgelöst wird, sobald das Element existiert.

8. Dynamische Routen und Params mit Props

Dynamische Routensegmente wie /blog/:slug oder /produkte/:categoryId/:productId sind das Werkzeug für parameterbasierte Ansichten. Der Parameter ist in der Komponente über route.params.slug zugänglich. Eleganter und besser testbar ist die Props-Option: Mit props: true auf der Routen-Definition werden die Params als Component Props übergeben. Die Komponente muss dann nur noch ein Prop slug: string deklarieren und ist vollständig vom Router-System entkoppelt – sie funktioniert auch ohne Router, was Unit-Tests erheblich vereinfacht.

Für komplexere Transformationen unterstützt props auch Funktionen: props: (route) => ({ id: parseInt(route.params.id as string) }) konvertiert den immer als String vorliegenden URL-Parameter in die richtige TypeScript-Type. Optional-Chaining und Fallback-Werte in dieser Funktion verhindern, dass undefinierte Parameter zu Laufzeitfehlern führen. Bei Routen mit Query-String-Parametern eignet sich useRoute().query kombiniert mit einem Watcher, der API-Aufrufe bei Parameteränderungen auslöst – das ermöglicht filterbare Listen ohne Seitenreload.

9. Routing-Strategien im Vergleich

Die Wahl der richtigen Routing-Strategie beeinflusst Wartbarkeit, Performance und Sicherheit der Vue Router-Konfiguration erheblich. Folgende Tabelle vergleicht die wichtigsten Ansätze:

Strategie Ansatz Empfehlung Vorteil
Auth-Check if(!auth) in jedem Component Globaler beforeEach-Guard Zentral, kein Duplizieren
Params nutzen useRoute().params.id in Komponente props: true in Route Komponente router-unabhängig, testbar
Code-Splitting Alle Imports eager Dynamic Import per Route Kleineres Initial-Bundle
Scroll Kein scrollBehavior scrollBehavior mit savedPosition Korrekte Back-Navigation
Layouts Layout in jeder View dupliziert Nested Routes mit Layout-Komponente Kein Re-Mount bei Navigation

Die konsequente Nutzung von Meta-Feldern für Berechtigungen, Props für Parameter und Nested Routes für Layouts sind die drei Maßnahmen, die den größten Qualitätssprung in einer Vue Router-Konfiguration bringen. Zusammen machen sie die Anwendung testbarer, die Konfiguration lesbarer und den Code wartbarer – ohne nennenswerten zusätzlichen Aufwand in der Implementierung.

Mironsoft

Vue.js-Entwicklung, SPA-Architektur und Frontend-Engineering

Vue Router-Architektur für euer Projekt?

Wir analysieren bestehende Vue-Anwendungen, identifizieren Router-Antipatterns und bauen eine saubere Routing-Struktur mit Guards, Lazy Loading und korrektem Scroll-Verhalten.

Router-Audit

Analyse bestehender Route-Konfigurationen auf Sicherheit, Performance und Wartbarkeit

Guard-Implementierung

Rollenbasierte Navigation Guards mit Pinia-Integration und TypeScript-Typisierung

Lazy Loading

Code-Splitting-Strategie für optimale Initial-Ladezeit und Bundle-Größen

10. Zusammenfassung

Vue Router wird erst dann vollständig genutzt, wenn Nested Routes, Navigation Guards, Lazy Loading und Scroll Behavior gezielt eingesetzt werden. Nested Routes ermöglichen Layout-Hierarchien ohne Component-Re-Mount. Der globale beforeEach-Guard mit Meta-Feldern hält Zugriffslogik zentral. Dynamic Imports reduzieren das Initial-Bundle auf das wirklich Notwendige. Die scrollBehavior-Funktion macht Back-Navigation und Anker-Links korrekt.

Der entscheidende Qualitätssprung kommt durch TypeScript-Typisierung der Meta-Felder, Props statt direktem useRoute() in Komponenten und das konsequente Gruppieren zusammengehöriger Routen in Chunks. Eine Vue Router-Konfiguration, die diese Muster befolgt, ist nicht nur sicherer und schneller – sie ist auch erheblich einfacher zu testen, weil Komponenten nicht vom Router abhängig sind.

Vue Router: Nested Routes, Guards und Scroll Behavior — Das Wichtigste

Nested Routes

Layout-Komponenten mit <router-view /> als Children – kein Re-Mount von Navigations- und Footer-Elementen bei Routenwechseln.

Navigation Guards

Globaler beforeEach mit Meta-Feldern für Auth und Rollen – zentral, deklarativ, ohne Duplizierung in Komponenten.

Lazy Loading

Dynamic Imports für alle Views außer der kritischen Startseite – gruppierbar per Magic Comment für zusammengehörige Bereiche.

Scroll Behavior

scrollBehavior mit savedPosition für Browser-Back, to.hash für Anker und { top: 0 } als Standard – macht SPAs navigierbar wie klassische Webseiten.

11. FAQ: Vue Router – Nested Routes, Guards und Scroll Behavior

1Was sind Nested Routes in Vue Router?
Verschachtelte Routen-Definitionen mit Layout-Komponenten und <router-view /> als Children – das Layout bleibt, nur der Inhalt wechselt, kein Re-Mount von Navigation und Footer.
2beforeEach vs. beforeEnter – wann was?
beforeEach für übergreifende Logik (Auth, Titel, Analytics), beforeEnter für routen-spezifische Prüfungen, die nur für einzelne Routen gelten.
3Wie funktioniert Lazy Loading in Vue Router?
Dynamic Imports statt statischer: component: () => import('@/views/MyView.vue'). Vite/Webpack erstellen separate Chunks, die nur bei erstem Aufruf der Route geladen werden.
4Wie korrigiere ich das Scroll-Verhalten?
Mit scrollBehavior beim Router-Erstellen: savedPosition für Back/Forward, { el: to.hash } für Anker, { top: 0 } als Standard.
5Route-Params als Props übergeben?
props: true in der Routen-Definition – die Komponente deklariert ein gleichnamiges Prop und ist vom Router entkoppelt und damit einfacher testbar.
6Welcher History-Modus für Produktion?
createWebHistory für saubere URLs ohne Hash – mit Server-Fallback auf index.html. createWebHashHistory nur wenn kein Server-Setup möglich ist.
7TypeScript-Typisierung von Meta-Feldern?
Module Augmentation: declare module 'vue-router' { interface RouteMeta { requiresAuth?: boolean } } – damit sind alle Meta-Felder typisiert und ohne Casts nutzbar.
8Seite mit ungespeicherten Daten verlassen verhindern?
onBeforeRouteLeave aus der Composition API – zeigt Bestätigungsdialog und ruft next(true/false) je nach Antwort des Nutzers auf.
9Routen-Chunks in Vite gruppieren?
Via build.rollupOptions.output.manualChunks in vite.config.ts oder per Magic Comment /* webpackChunkName: 'admin' */ im Dynamic Import.
10Vue Router ohne Browser-Umgebung?
createMemoryHistory für SSR und Tests ohne Browser – kein URL-Zugriff, rein speicherbasiert. Standard für Nuxt.js-SSR und Vitest-Tests mit Router.