Übungsaufgaben zu Build und Auslieferung einer Vue-App: Mitfahrgesuche im Intranet

https://bildung.social/@oerinformatik/

https://oer-informatik.de/vue-build-auslieferung-uebungsaufgaben

Vue 3, Vite, Produktions-Build, Umgebungsvariablen, base, History-Mode, Deep Link, nginx, try_files, Caching, Übungsaufgaben, Prüfungsvorbereitung

tl/dr; (ca. 47 min Lese- und Bearbeitungszeit): Die Web-App des Mitfahrlabors soll zusätzlich im Intranet der Schule erscheinen, und die Ansicht der Mitfahrgesuche soll sich je Umgebung ein- und ausschalten lassen. In diesen Übungen liest Du die Ausgabe eines Produktions-Builds, entwickelst einen Schalter über eine Umgebungsvariable mit Tests, lieferst die App mit nginx in einem Unterordner aus und behebst eine Auslieferung, bei der Deep Links mit 404 enden und Nutzende alte Versionen sehen [AI-Assisted]. (Zuletzt geändert am 11.11.2026)

Dieser Artikel ist Teil einer Artikelreihe über die Web-App mit Vue 3 und TypeScript: Projektübersicht / XP-Projektstart / TypeScript für Java-Entwickler*innen / Komponenten testgetrieben / API-Client aus OpenAPI / Router und Continuous Integration / Formulare und Validierung / Refactoring mit Pinia / Anmeldung mit Keycloak / Fahrer-Sicht und Coding Standards / Build und Auslieferung / End-to-End-Tests mit Playwright / Change Request Live-Aktualisierung

Überblick: npm run build macht aus src und .env.production den Ordner dist mit index.html und Dateien mit Hash im Namen. nginx verteilt die Anfragen: /v1 an Spring Boot, Pfade der App per try_files auf index.html ohne Cache, /assets mit einem Jahr Cache (mit KI erstellt)

Diese Übungsaufgaben gehören zum Artikel Build und Auslieferung einer Vue-App: dist/, nginx und Deep Links. Alle Aufgaben spielen im Projektteam der Mitfahr-App Mitfahrlabor. Ausgangspunkt für die Aufgaben 2.1 und 2.2 ist das Projekt mitfahrlabor-web im Ordner src/10-vue-build-auslieferung des Repositorys dieser Reihe mit .env.production, der Routentabelle in src/router/index.ts, der nginx-Konfiguration und dem Rauchtest im Ordner nginx/. Was für die Gesuche fehlt, steht vollständig in den Aufgaben; die Lösungen der Übungen zu den vorigen Artikeln brauchst Du nicht.

Für die Gesuche nimmt das Team wie in den vorigen Übungen an, dass das Backend in einer späteren Version GET /v1/gesuche anbietet. Bis dahin soll die Ansicht der Gesuche im Entwicklungsserver sichtbar sein, im ausgelieferten Build aber nicht.

Hinweise zur Bearbeitung: Bis zur Teil-1-Prüfung der IT-Berufe solltest Du die Basiskompetenz beherrschen, also Aufgabe 1.1. Als FIA, FIDP oder FIDV musst Du zur Teil-2-Prüfung Aufgaben auf Kernkompetenz-Ebene lösen können, das sind Aufgabe 2.1 und Aufgabe 2.2. Von FIA wird zusätzlich erwartet, dass ihnen Aufgaben auf Profilkompetenz-Ebene (Aufgabe 3.1) keine Probleme bereiten. Die Operatoren (nennen, erklären, vorhersagen, entwickeln, konfigurieren, analysieren, beurteilen …) zeigen, wie ausführlich Du antworten sollst. Code schreibst Du in TypeScript und prüfst ihn mit npm test, npm run type-check, npm run lint und npm run format:check; eine nginx-Konfiguration prüfst Du, wenn Du nginx installiert hast, mit nginx -t. Schreib Deine Lösung zuerst vollständig auf, bevor Du sie mit der Beispiellösung vergleichst.

Du möchtest eigene Aufgaben zum Üben von einem KI-Sprachmodell generieren lassen?

Kopiere den Text in ein KI-Sprachmodell Deiner Wahl und passe Kompetenzstufe, Anzahl, Bearbeitungszeit und Szenario bei Bedarf an. Wie die Aufgaben aufgebaut sind, beschreibt der Artikel Wie sind die Aufgaben aufgebaut?. Prüfe die Ergebnisse mit dem Lehrartikel: KI-Sprachmodelle machen Fehler.

Erstelle neue Übungsaufgaben zum Thema „Build und Auslieferung einer
Vue-App: Mitfahrgesuche im Intranet“.

Bauplan: Halte Dich an Aufbau, Kompetenzstufen, Operatoren,
Anforderungsbereiche, Punkte und Zeitschätzung, die im Artikel
https://oer-informatik.de/aufgaben-aufbau beschrieben sind.

Fachliche Grundlage:
- https://oer-informatik.de/vue-build-auslieferung
- https://oer-informatik.de/szenario-mitfahrlabor
- Stilbeispiel: https://oer-informatik.de/vue-build-auslieferung-uebungsaufgaben

Die Aufgaben sollen Inhalte zu diesen Schlagwörtern abdecken:
- Vue 3
- Vite
- Produktions-Build
- Umgebungsvariablen
- base
- History-Mode
- Deep Link
- nginx
- try_files
- Caching

Kompetenzstufe: Kernkompetenz (alternativ: Basiskompetenz, Profilkompetenz, Expertise)
Anzahl: 1 Aufgabe mit Teilaufgaben
Bearbeitungszeit in Minuten ca.: <5 bis 30 min>
Szenario: eines der Szenarien aus dem Bauplan

Vorgehen:
1. Lies zuerst die verlinkten Artikel. Kannst Du keine Webseiten
   abrufen, sag mir das, statt zu raten.
2. Steht bei einer Angabe noch ein Platzhalter in <…>, frag mich
   zuerst danach. Richte Punkte und Zahl der Teilaufgaben nach der
   Bearbeitungszeit (ca. 0,9 min je Punkt).
3. Gib je Aufgabe Titel mit Kompetenzstufe, Ausgangssituation und
   Teilaufgaben mit Operator und Punktzahl aus, dazu die geschätzte
   Bearbeitungszeit.
4. Gib die Beispiellösungen mit Punkteverteilung erst aus, wenn ich
   danach frage.

Aufgabe 1.1: Die Build-Ausgabe lesen (Basiskompetenz)

Jonas Wolff aus dem Projektteam baut die Web-App zum ersten Mal selbst mit npm run build. Vite meldet:

dist/index.html                               0.44 kB │ gzip:  0.28 kB
dist/assets/index-CqLr8KVv.css              230.06 kB │ gzip: 30.68 kB
dist/assets/NichtGefundenSeite-DLXe-ZPt.js    0.40 kB │ gzip:  0.30 kB
dist/assets/AnmeldungRueckkehr-DWoSgHz6.js    0.63 kB │ gzip:  0.43 kB
dist/assets/problem-odPE36Kg.js               1.27 kB │ gzip:  0.71 kB
dist/assets/AngebotSeite-krQG8s6B.js          6.22 kB │ gzip:  2.37 kB
dist/assets/AngebotNeuSeite-OwAe5F48.js       6.67 kB │ gzip:  2.37 kB
dist/assets/index-CE0DqqM_.js               187.91 kB │ gzip: 63.35 kB

In der Routentabelle sind die Suche (/) direkt importiert, die Detailseite, das Formular „Fahrt anbieten“, die Rückkehr von der Anmeldung und die Seite „nicht gefunden“ mit () => import(…). problem-odPE36Kg.js enthält die Auswertung der Problem Details, die Detailseite und das Formular beide brauchen.

a) Nenne die Dateien aus dist/, die der Browser beim ersten Aufruf der Suche / lädt, und die Dateien, die er zusätzlich lädt, wenn Jonas danach auf „Details“ zu einer Fahrt klickt. Erkläre, warum problem-odPE36Kg.js eine eigene Datei ist. (3 Punkte)

Beim ersten Aufruf von /: index.html, dann das Haupt-Bundle assets/index-CE0DqqM_.js und das Stylesheet assets/index-CqLr8KVv.css (dazu favicon.ico aus public/, das in der Liste nicht erscheint) (1 Punkt).

Beim Klick auf „Details“: assets/AngebotSeite-krQG8s6B.js und assets/problem-odPE36Kg.js (1 Punkt); danach die Daten über /v1/angebote/….

problem-…js ist ein gemeinsamer Chunk: Zwei nachgeladene Seiten brauchen denselben Code. Steckte er in beiden, würde er doppelt geladen; stünde er im Haupt-Bundle, müsste ihn jede Person schon beim Start laden, auch wenn sie nie eine Detailseite öffnet (1 Punkt).

b) Erkläre, was die Spalte gzip angibt, und welche der beiden Zahlen für die Ladezeit zählt. (2 Punkte)

Die Spalte zeigt die ungefähre Größe, wenn die Datei mit gzip komprimiert übertragen wird; auf der Platte liegt die Datei unkomprimiert mit der ersten Zahl (1 Punkt). Für die Ladezeit zählt die gzip-Zahl, aber nur, wenn der Webserver komprimiert überträgt (in nginx gzip on mit passenden gzip_types); sonst wird die volle Größe übertragen (1 Punkt).

c) Jonas ändert in AngebotSeite.vue eine Beschriftung und baut erneut. Sag voraus, welche Dateinamen sich sicher ändern und welche sicher gleich bleiben, und begründe kurz. (3 Punkte)

Sicher neu: AngebotSeite-….js, weil sich sein Inhalt und damit der Hash ändert (1 Punkt), und das Haupt-Bundle index-….js, weil es die Namen der nachzuladenden Chunks enthält (1 Punkt). index.html behält den Namen, verweist aber auf das neue Haupt-Bundle.

Sicher gleich: index-CqLr8KVv.css und problem-odPE36Kg.js, ihr Inhalt ist unverändert (1 Punkt).

Hinweis: In Stand 10 ändern sich zusätzlich AngebotNeuSeite-…, AnmeldungRueckkehr-… und NichtGefundenSeite-…, denn sie importieren aus dem Haupt-Bundle, dessen Name sich geändert hat. Wer das vorhersagt, hat den Mechanismus verstanden; für die Punkte genügen die sicheren Fälle.

d) Entscheide, ob die Aussagen stimmen, und begründe jede mit einem Satz: (1) Auf dem Server muss Node.js laufen, damit die App funktioniert. (2) index.html darf wie die Dateien unter assets/ ein Jahr im Browser-Cache bleiben. (3) Den Wert aus VITE_OIDC_AUTHORITY kann jede Person im Browser lesen. (4) Ändert man .env.production auf dem Server, gilt der neue Wert nach dem Neuladen der Seite. (2 Punkte)

Je richtig entschiedener und begründeter Aussage ½ Punkt:

  1. Falsch: dist/ enthält nur statische Dateien, jeder Webserver kann sie ausliefern; der Code läuft im Browser.
  2. Falsch: index.html behält bei jedem Release ihren Namen; mit langer Cache-Dauer sähen Nutzende weiter die alte Version. Sie bekommt no-cache.
  3. Richtig: Vite schreibt den Wert beim Bauen als Text ins JavaScript, das jede Person herunterlädt.
  4. Falsch: Die Werte werden beim Bauen festgeschrieben; ohne neuen Build ändert sich nichts.

Aufgabe 2.1: Ein Schalter für die Gesuche (Kernkompetenz)

Die Ansicht „Meine Mitfahrgesuche“ ist noch nicht fertig, und das Backend kennt die Gesuche noch nicht. Damit das Paar trotzdem auf dem Hauptzweig weiterarbeiten kann (Continuous Integration), soll eine Umgebungsvariable VITE_GESUCHE_ANZEIGEN die Route /gesuche ein- oder ausschalten: im Entwicklungsserver an, im Produktions-Build aus. Als Startcode legst Du die Seite src/views/GesucheSeite.vue an:

<template>
  <h2>Meine Mitfahrgesuche</h2>
  <p>Hier erscheinen bald Deine Gesuche.</p>
</template>

a) Lege die Variable in den passenden .env-Dateien fest und ergänze ihren Typ in env.d.ts. (3 Punkte)

Neue Datei .env.development (1 Punkt):

VITE_GESUCHE_ANZEIGEN=true

Ergänzung in .env.production (1 Punkt):

# Ansicht der Mitfahrgesuche: aus, bis das Backend /v1/gesuche anbietet
VITE_GESUCHE_ANZEIGEN=false

In env.d.ts im Interface ImportMetaEnv (1 Punkt), optional und als Text, weil .env-Dateien nur Text kennen:

  /** Ansicht der Mitfahrgesuche: 'true' = an, sonst aus */
  readonly VITE_GESUCHE_ANZEIGEN?: string

Die Variable muss mit VITE_ beginnen, sonst nimmt Vite sie nicht in import.meta.env auf. Ein Eintrag in .env statt .env.development wäre auch möglich, dann muss .env.production ihn überschreiben.

b) Entwickle in src/domain/schalter.ts die Funktion gesucheAktiv(env = import.meta.env): boolean. Nur der Text true schaltet ein; fehlt die Variable oder steht etwas anderes darin, bleibt die Ansicht aus. (3 Punkte)

/** Umgebungsvariablen, die die Schalter lesen (im Test als einfaches Objekt übergeben) */
type Schalter = Pick<ImportMetaEnv, 'VITE_GESUCHE_ANZEIGEN'>

/** Gesuche anzeigen? Nur der Text 'true' schaltet ein; fehlt die Variable, bleibt die Ansicht aus. */
export function gesucheAktiv(env: Schalter = import.meta.env): boolean {
  return env.VITE_GESUCHE_ANZEIGEN === 'true'
}

Punkte: Vergleich mit dem Text 'true' statt Umwandlung mit Boolean(...) (der Text 'false' wäre sonst true) (1 Punkt), fehlende Variable ergibt false (1 Punkt), Parameter mit Voreinstellung import.meta.env und passendem Typ, damit Tests ein eigenes Objekt übergeben können (1 Punkt).

c) Ergänze in src/router/index.ts eine Funktion gesucheRouten(aktiv = gesucheAktiv()): RouteRecordRaw[], die nur bei eingeschaltetem Schalter die Route /gesuche mit nachgeladener GesucheSeite liefert, und baue sie in die Routentabelle ein. (2 Punkte)

import { gesucheAktiv } from '@/domain/schalter'

/** Route der Gesuche nur, wenn der Schalter an ist */
export function gesucheRouten(aktiv: boolean = gesucheAktiv()): RouteRecordRaw[] {
  return aktiv ? [{ path: '/gesuche', name: 'gesuche', component: () => import('@/views/GesucheSeite.vue') }] : []
}

In routes vor der Route „nicht gefunden“ (die Reihenfolge ist für den Router egal, aber so bleibt die Tabelle lesbar):

  ...gesucheRouten(),
  { path: '/:pfad(.*)*', name: 'nicht-gefunden', component: () => import('@/views/NichtGefundenSeite.vue') },

Funktion mit beiden Fällen (1 Punkt), Einbau per Spread in die Tabelle (1 Punkt). Ist der Schalter aus, landet /gesuche auf der Seite „nicht gefunden“.

d) Entwickle in src/domain/__tests__/schalter.spec.ts Tests mit Vitest: verschiedene Werte für die Funktion aus b), einmal den Weg über import.meta.env mit vi.stubEnv() und beide Fälle von gesucheRouten(). (3 Punkte)

import { afterEach, describe, expect, it, vi } from 'vitest'
import { gesucheAktiv } from '@/domain/schalter'
import { gesucheRouten } from '@/router'

describe('Schalter für die Gesuche', () => {
  afterEach(() => vi.unstubAllEnvs())

  it('ist nur mit dem Text true an', () => {
    expect(gesucheAktiv({ VITE_GESUCHE_ANZEIGEN: 'true' })).toBe(true)
    expect(gesucheAktiv({ VITE_GESUCHE_ANZEIGEN: 'false' })).toBe(false)
    expect(gesucheAktiv({ VITE_GESUCHE_ANZEIGEN: 'ja' })).toBe(false)
    expect(gesucheAktiv({})).toBe(false)
  })

  it('liest ohne Argument import.meta.env', () => {
    vi.stubEnv('VITE_GESUCHE_ANZEIGEN', 'true')
    expect(gesucheAktiv()).toBe(true)
    vi.stubEnv('VITE_GESUCHE_ANZEIGEN', 'false')
    expect(gesucheAktiv()).toBe(false)
  })

  it('meldet die Route /gesuche nur bei eingeschaltetem Schalter an', () => {
    expect(gesucheRouten(true).map((r) => r.path)).toEqual(['/gesuche'])
    expect(gesucheRouten(false)).toEqual([])
  })
})

Je Test 1 Punkt. vi.unstubAllEnvs() stellt nach jedem Test die ursprünglichen Werte wieder her, damit andere Tests nicht beeinflusst werden. Vitest läuft im Modus test; .env.development und .env.production gelten dort nicht, deshalb setzt der Test die Werte selbst.

e) Andrea Wolf, die Datenschutzbeauftragte der Schule, fragt, ob die Gesuche damit vor neugierigen Blicken sicher sind, solange der Schalter im Produktions-Build aus ist. Erkläre, was der Schalter bewirkt und was nicht. (2 Punkte)

Der Schalter blendet nur die Route aus: Wer /gesuche aufruft, landet auf „nicht gefunden“ (1 Punkt). Der Code der Seite wird trotzdem gebaut und ausgeliefert (im Build steht assets/GesucheSeite-….js), und der Wert false steht lesbar im Haupt-Bundle; wer die Dateien ansieht, findet die Seite. Ein Schalter in der Oberfläche ist eine Hilfe für Releases, kein Schutz. Daten schützt nur das Backend, und das bietet die Gesuche ohnehin noch nicht an (1 Punkt).

Aufgabe 2.2: Auslieferung im Unterordner des Intranets (Kernkompetenz)

Svenja Krämer von der Schul-IT OWL betreut das Intranet der Schule unter https://intranet.dbh.example. Sie möchte die Web-App zusätzlich unter https://intranet.dbh.example/mitfahrlabor/ anbieten. Auf ihrem Server läuft nginx, das Backend erreicht er intern unter http://127.0.0.1:8080, die Dateien der App sollen nach /srv/intranet/mitfahrlabor/ kommen. Die vorhandene Konfiguration (Ausschnitt):

server {
    listen 443 ssl;
    server_name intranet.dbh.example;
    # ssl_certificate ... (vorhanden)

    root /srv/intranet;                 # Startseite und Seiten des Intranets

    location / {
        try_files $uri $uri/ =404;      # Intranet: nur vorhandene Dateien
    }
}

a) Gib den Befehl an, mit dem Du die App für diesen Ort baust, und erkläre, was sich dadurch in dist/index.html und im Router ändert. (3 Punkte)

npm run build -- --base=/mitfahrlabor/

Alternativ base: '/mitfahrlabor/' in vite.config.ts, dann gilt es aber auch für den Teamserver (1 Punkt).

In dist/index.html stehen die Verweise jetzt mit Unterordner, etwa src="/mitfahrlabor/assets/index-….js" und href="/mitfahrlabor/favicon.ico"; auch die Adressen der nachgeladenen Chunks beginnen mit /mitfahrlabor/ (1 Punkt). import.meta.env.BASE_URL ist /mitfahrlabor/; createWebHistory(import.meta.env.BASE_URL) legt die Routen darunter, /angebote/1 heißt also /mitfahrlabor/angebote/1. Seit Stand 10 enthält auch die Rückkehr-Adresse der Anmeldung den Unterordner (1 Punkt).

b) Ergänze die Konfiguration um die Blöcke für die API, die Assets der App und die App selbst, mit Fallback für Deep Links und passenden Cache-Headern. Das Intranet darf sich dadurch nicht ändern. (6 Punkte)

    # API: wie auf dem Teamserver an das Backend weiterreichen
    location /v1/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Assets der App: ein Jahr Cache, fehlende Dateien ehrlich 404
    location /mitfahrlabor/assets/ {
        try_files $uri =404;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

    # App: Dateien oder die App-Hülle des Unterordners
    location /mitfahrlabor/ {
        try_files $uri $uri/ /mitfahrlabor/index.html;
        add_header Cache-Control "no-cache";
    }

Punkte: Proxy für /v1/ (1 Punkt); Assets mit =404 und immutable (2 Punkte); App-Block mit Fallback auf /mitfahrlabor/index.html, nicht /index.html (das wäre die Startseite des Intranets) (2 Punkte); no-cache für die Hülle, location / des Intranets unverändert (1 Punkt).

Mit root /srv/intranet sucht nginx /mitfahrlabor/assets/x.js unter /srv/intranet/mitfahrlabor/assets/x.js, ein alias ist nicht nötig. Wer /mitfahrlabor ohne Schrägstrich aufruft, bekommt von nginx eine Umleitung auf /mitfahrlabor/, weil es den Ordner gibt. Die Konfiguration ist mit nginx 1.24 geprüft; Deep Links wie /mitfahrlabor/angebote/1 liefern 200 mit der App-Hülle.

c) Nenne die Einträge, die beim Client mitfahrlabor-web in Keycloak dazukommen, und was ohne sie bei der Anmeldung passiert. (2 Punkte)

Redirect URI https://intranet.dbh.example/mitfahrlabor/anmeldung/rueckkehr, Web Origin https://intranet.dbh.example und als Adresse nach dem Abmelden https://intranet.dbh.example/mitfahrlabor/ (1 Punkt). Ohne Redirect URI zeigt Keycloak statt der Anmeldeseite die Fehlermeldung Invalid parameter: redirect_uri; ohne Web Origin scheitert das Abholen des Tokens an der CORS-Prüfung des Browsers (1 Punkt).

d) Svenja startet den Rauchtest mit bash nginx/pruefe-auslieferung.sh https://intranet.dbh.example/mitfahrlabor. Sag voraus, welche Prüfung scheitert, erkläre warum und gib den richtigen Aufruf an. (3 Punkte)

Ohne zweiten Parameter prüft das Skript die API unter https://intranet.dbh.example/mitfahrlabor/v1/haltepunkte. Diesen Pfad fängt der Block location /mitfahrlabor/ mit dem Fallback ab: Status 200, aber mit der App-Hülle. „API über den Proxy erreichbar“ meldet deshalb ok, „API liefert JSON, nicht die App-Hülle“ scheitert mit erhalten '<' (2 Punkte). Die API liegt am Wurzelpfad des Servers, nicht im Unterordner. Richtiger Aufruf (1 Punkt):

bash nginx/pruefe-auslieferung.sh https://intranet.dbh.example/mitfahrlabor https://intranet.dbh.example

Ein anderes Paar hat die App vor Svenja schon einmal im Intranet ausgeliefert. Es hat mit npm run build gebaut, dist/ nach /srv/intranet/mitfahrlabor/ kopiert und diesen Block ergänzt:

    location /mitfahrlabor/ {
        try_files $uri $uri/ =404;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

Danach kamen vier Rückmeldungen, die zweite bis vierte erst, nachdem das Paar Symptom 1 notdürftig behoben hatte:

  1. Aufruf von https://intranet.dbh.example/mitfahrlabor/: Die Seite bleibt weiß. In den Entwicklerwerkzeugen steht GET https://intranet.dbh.example/assets/index-WD49qn96.js mit Status 404.
  2. Wer in der App auf eine Fahrt klickt, sieht die Detailseite. Drückt Aylin Kaya dort F5 auf /mitfahrlabor/angebote/4, zeigt nginx „404 Not Found“.
  3. Nach einem Release am Montag sieht Thomas Becker am Mittwoch immer noch die alte Version. Nach Strg+F5 erscheint die neue.
  4. Nach der Anmeldung bei Keycloak erscheint statt der App die Meldung Invalid parameter: redirect_uri.

a) Analysiere die vier Symptome: Nenne jeweils die Ursache und erläutere, wie sie zu genau diesem Symptom führt. (8 Punkte)

Je Symptom 1 Punkt für die Ursache, 1 Punkt für die Erläuterung:

  1. Gebaut ohne base. index.html verweist mit /assets/index-….js auf den Wurzelpfad des Servers. Dort gibt es keinen Ordner assets, nginx antwortet mit 404, die App startet nicht, und <div id="app"> bleibt leer.
  2. Kein Fallback. try_files $uri $uri/ =404 liefert nur vorhandene Dateien. Beim Klicken ändert der Router die Adresse ohne den Server; beim Neu laden fragt der Browser nach /mitfahrlabor/angebote/4, eine solche Datei gibt es nicht.
  3. index.html mit langer Cache-Dauer. Die Regel max-age=31536000, immutable gilt für den ganzen Block, also auch für index.html. Der Browser nimmt die gespeicherte alte Hülle, ohne nachzufragen, und lädt damit die alten Bundles (die noch auf dem Server liegen). Erst Strg+F5 erzwingt eine neue Anfrage.
  4. Redirect URI fehlt im Realm. Die App schickt https://intranet.dbh.example/mitfahrlabor/anmeldung/rueckkehr als Rückkehr-Adresse; Keycloak leitet nur an eingetragene Adressen zurück und bricht sonst ab. (Mit einem Stand vor 10 hätte die App sogar …/anmeldung/rueckkehr ohne Unterordner geschickt; dann hätte auch der Eintrag mit Unterordner nicht geholfen.)

b) Gib die korrigierte Konfiguration und den Build-Befehl an. (5 Punkte)

Build mit Unterordner (1 Punkt):

npm run build -- --base=/mitfahrlabor/

Konfiguration: Assets getrennt mit langer Cache-Dauer (2 Punkte), App mit Fallback auf die eigene Hülle und no-cache (2 Punkte):

    location /mitfahrlabor/assets/ {
        try_files $uri =404;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

    location /mitfahrlabor/ {
        try_files $uri $uri/ /mitfahrlabor/index.html;
        add_header Cache-Control "no-cache";
    }

Dazu kommt der Eintrag der Redirect URI im Realm (siehe Aufgabe 2.2 c). Wer Symptom 3 schon hatte: Die alte index.html liegt bei den Betroffenen noch bis zu einem Jahr im Cache; einmal Strg+F5 oder das Leeren des Caches ist nötig. Eine solche Panne lässt sich nachträglich nicht vom Server aus beheben.

c) Für Symptom 2 schlägt jemand try_files $uri $uri/ /index.html; vor, „wie im Artikel“. Beurteile den Vorschlag. (2 Punkte)

Der Vorschlag beseitigt die 404, liefert aber die falsche Seite: /index.html ist hier die Startseite des Intranets unter /srv/intranet/index.html, nicht die App-Hülle. Aylin sähe beim Neu laden das Intranet statt der Fahrt (1 Punkt). Im Artikel liegt die App am Wurzelpfad ihrer eigenen Domain, deshalb passt dort /index.html. Im Unterordner muss der Fallback /mitfahrlabor/index.html heißen; der Rauchtest mit der Prüfung „Deep Link liefert die App-Hülle“ hätte den Fehler gefunden (1 Punkt).


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Übungsaufgaben zu Build und Auslieferung einer Vue-App: Mitfahrgesuche im Intranet” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/vue-build-auslieferung-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/programmierung/vue. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 11.11.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: