Modul 7: Vue Ökosystem

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.

router/index.ts
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 router
main.ts
import { 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.

App.vue
<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.

router/index.ts
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'),
  },
]
UserProfile.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.

router/index.ts
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'),
      },
    ],
  },
]
UserLayout.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

router/index.ts
// 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

AdminDashboard.vue
<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.

LoginForm.vue
<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.

router/index.ts
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.

router/index.ts
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.

Vue Router Simulation
Interaktiv
URL: /

🏠 Startseite

Willkommen! Dies ist die Home-Route (/).

💡 In einer echten App würde <RouterView> die Komponente rendern und die Browser-URL sich ändern.

Zusammenfassung

Vue Router Cheatsheet
// 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-Änderung
💡

Das Wichtigste

  • createRouter definiert Pfad-zu-Komponente-Mappings.
  • RouterView rendert die aktive Route, RouterLink navigiert.
  • Dynamische Params (:id) und Query (?page=2) über useRoute().
  • Nested Routes via children fü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/.