Wem gehört die URL?
Eine Route, zwei Betriebsarten
Abschnitt betitelt „Eine Route, zwei Betriebsarten“In einem Architektur-Lab besaß ein Angular-Remote eine technisch einfache Route:
/detailsEigenständig gestartet war sie unter einer ebenso einfachen URL erreichbar:
https://remote.example/detailsInnerhalb der Produktshell wurde dasselbe Remote jedoch unter einem zusätzlichen Produktbereich eingebunden:
https://product.example/tasks/detailsDas Remote sollte weiterhin selbst definieren, was details bedeutet und welche Ansicht dort aktiviert wird. Gleichzeitig musste die Produktshell eine konsistente Produkt-URL erzeugen. Genau an dieser Grenze entstand ein unscheinbares Routingproblem.
Die Navigation im Remote sah zunächst so aus:
router.navigate(['details']);Im Standalone-Modus konnte daraus wie erwartet werden:
/create → /detailsInnerhalb der Produktshell wurde derselbe Befehl jedoch vom Router-Root aus aufgelöst:
/tasks/create → /detailsDas Ergebnis war technisch nachvollziehbar, aber fachlich falsch. Erwartet war:
/tasks/create → /tasks/detailsEine naheliegende Gegenmaßnahme hätte darin bestanden, den Host-Präfix direkt in das Remote einzubauen:
router.navigate(['/tasks', 'details']);Damit wäre das konkrete Problem innerhalb der Produktshell gelöst gewesen. Gleichzeitig hätte das Remote im Standalone-Modus eine Route erzeugt, die dort nicht existiert:
/tasks/detailsDie funktionierende Lösung bestand nicht darin, einen der beiden vollständigen Pfade zum einzig richtigen Pfad zu erklären. Stattdessen wurde die Navigation relativ zum Mount-Kontext des Remotes ausgeführt:
router.navigate(['details'], { relativeTo: remoteShellRoute,});Der Navigationsauftrag blieb in beiden Betriebsarten identisch. Nur der Kontext, in dem er aufgelöst wurde, unterschied sich:
Standalone: /create → /details
Im Host: /tasks/create → /tasks/detailsDas Beispiel wirkt zunächst wie eine Besonderheit des Angular Routers. Tatsächlich verweist es auf eine allgemeinere Architekturfrage:
Wie können Host und Remote dieselbe fachliche Navigation unterstützen, obwohl sie unter unterschiedlichen äußeren URLs betrieben werden?
Die Antwort beginnt nicht bei navigate, basename oder pushState. Sie beginnt bei der Verantwortung für die URL.
Eine URL ist ein Produktvertrag
Abschnitt betitelt „Eine URL ist ein Produktvertrag“Eine Browser-URL erfüllt in einer zusammengesetzten Anwendung mehrere Aufgaben gleichzeitig:
Browser-URL├── adressiert das Produkt├── bestimmt den Einstiegspunkt├── wählt einen Produktbereich├── adressiert möglicherweise fachlichen Zustand├── erzeugt Browser-History└── soll nach Reload rekonstruierbar seinSie ist externer Link, Bookmark, Authentifizierungsziel, Einstieg in einen Produktbereich, History-Eintrag und beobachtbarer Ausschnitt des Produktzustands. In einer Microfrontend-Architektur ist sie zusätzlich ein Vertrag zwischen Produktshell und Remote.
Deshalb greift die Frage zu kurz, welcher Router oder welche Komponente die URL technisch verändert. Entscheidend ist, welcher Teil des Systems die Bedeutung eines URL-Abschnitts verantwortet.
Route Ownership ist keine Frage danach, welche Komponente eine Router-API aufruft. Sie ist die Frage, welcher Teil des Systems für welchen Abschnitt der kanonischen Produkt-URL verantwortlich ist.
Der technische Router kann die URL schreiben, ohne ihre vollständige fachliche Bedeutung zu besitzen. Ebenso kann ein Remote die Bedeutung seines Routenraums besitzen, obwohl die kanonische URL von einem Router der Produktshell erzeugt wird.
Navigation ist ein Intent. Die URL ist beobachtbarer Zustand.
Eine Navigation beschreibt zunächst, welches Ziel erreicht werden soll. Sobald dieses Ziel in die Browser-URL und die Browser-History geschrieben wurde, wird es zu einem Zustand, der später erneut interpretiert werden muss. Damit entstehen Anforderungen, die über den ursprünglichen Klick hinausgehen: direkter Aufruf, Reload, Login-Redirect, Remount und Browser-Zurück.
Der Host besitzt den äußeren Produktpfad
Abschnitt betitelt „Der Host besitzt den äußeren Produktpfad“Ein gut funktionierendes Ownership-Modell trennt den äußeren Produktbereich vom relativen Routenraum des Remotes.
Beispielsweise kann die Produktshell ein Remote unter /tasks einbinden. Innerhalb dieses Bereichs definiert das Remote einen eigenen Routenbaum:
Host└── /tasks
Remote└── projects ├── list ├── details └── completedDaraus entstehen abhängig von der Betriebsart unterschiedliche vollständige URLs.
Standalone:
/projects/projects/details/projects/completedInnerhalb der Produktshell:
/tasks/projects/tasks/projects/details/tasks/projects/completedDer Host besitzt in diesem Modell den Produkt-Origin, den äußeren Mount-Präfix, die Auswahl des Remotes und die Zusammensetzung der kanonischen Produkt-URL. Er muss wissen, unter welchem Produktpfad ein Remote erreichbar ist.
Das bedeutet nicht, dass der Host jede interne Route fachlich verstehen muss. Für das Mounting kann es genügen, den Produktbereich /tasks dem zuständigen Remote zuzuordnen. Was projects/details innerhalb dieses Bereichs bedeutet, bleibt Verantwortung des Remotes.
Der Host besitzt den äußeren Produktpfad. Das Remote besitzt seinen relativen Routenraum.
Dieses Modell ist besonders naheliegend, wenn Host und Remote denselben Router oder kompatible Mount-Mechanismen verwenden. Es ist jedoch keine universelle Vorgabe. Auch delegierte Routerbereiche oder Navigation Ports können dieselbe Trennung herstellen.
Das Remote besitzt seinen relativen Routenraum
Abschnitt betitelt „Das Remote besitzt seinen relativen Routenraum“Das Remote definiert seine relativen Unterpfade und deren fachliche Bedeutung. Es entscheidet beispielsweise, ob eine Projektübersicht unter projects, eine Detailansicht unter projects/details oder ein Archiv unter projects/completed erreichbar ist.
Damit besitzt es:
- die Zuordnung seiner Ansichten zu relativen Routen,
- die Bedeutung der Unterpfade,
- die interne Navigation innerhalb des Produktbereichs,
- die fachliche Rekonstruktion der adressierten Ansichten.
Was das Remote nicht besitzen muss, ist der äußere Mount-Präfix.
Der Host muss wissen, unter welchem Produktpfad ein Remote erreichbar ist. Das Remote muss nicht wissen, unter welchem äußeren Präfix es gerade gemountet wurde.
Diese Trennung schützt beide Seiten. Die Produktshell kann einen Produktbereich umbenennen oder an einer anderen Stelle mounten, ohne die interne Navigation des Remotes neu zu entwerfen. Das Remote kann eigenständig betrieben werden, ohne eine künstliche Host-Struktur nachbilden zu müssen.
Technische Pfad-Ownership und fachliche Zustands-Ownership sind dabei nicht dasselbe. Der Host kann /tasks besitzen und trotzdem nicht wissen, welche Daten eine Route wie projects/details benötigt. Umgekehrt kann das Remote die fachliche Bedeutung von details besitzen, ohne die vollständige Produkt-URL festzulegen.
Warum hartcodierte Host-Präfixe die Unabhängigkeit zerstören
Abschnitt betitelt „Warum hartcodierte Host-Präfixe die Unabhängigkeit zerstören“Dasselbe Muster zeigte sich im Architektur-Lab später bei einem weiteren Remote. Dort wurden zunächst relative Router-Kommandos verwendet:
['projects', 'detail'];Im Zuge der Host-Integration wurden daraus absolute Produktpfade:
['/tasks', 'projects', 'detail'];Innerhalb der Produktshell wirkte die Änderung zunächst stabil. Die Navigation erreichte wieder den erwarteten Produktbereich. Erst der eigenständige Betrieb zeigte die neue Abhängigkeit.
Das Remote erzeugte nun auch im Standalone-Betrieb:
/tasks/projects/detailDiese Route existierte dort nicht. Das Remote kannte plötzlich nicht mehr nur seine fachlichen Unterpfade, sondern auch den Namen und die URL-Struktur seines Hosts.
Damit veränderte sich mehr als ein Router-Kommando:
- Der Host-Präfix wurde Teil der Remote-Implementierung.
- Eine Änderung des Mount-Pfads erforderte eine Änderung im Remote.
- Standalone- und Host-Modus verwendeten nicht mehr dieselbe interne Navigation.
- Der Remote-Test prüfte nicht länger denselben Routingvertrag wie die Host-Integration.
- Der äußere Produktaufbau sickerte in die lokale Fachlichkeit.
Ein absoluter Pfad kann einen konkreten Integrationsfehler beheben und gleichzeitig die architektonische Unabhängigkeit beschädigen.
Der Fehler liegt nicht darin, dass absolute URLs grundsätzlich ungeeignet wären. Ein Produkt benötigt kanonische absolute URLs. Problematisch wird es, wenn ein fachlich eigenständiger Produktbereich den äußeren Composition-Pfad seines aktuellen Hosts als eigene Konstante behandelt.
Der Host-Pfad ist Konfiguration der Composition, nicht Fachwissen des Remotes.
Ein hartcodierter Host-Präfix macht ein Remote nicht integriert, sondern abhängig.
Relative Navigation benötigt einen Mount-Kontext
Abschnitt betitelt „Relative Navigation benötigt einen Mount-Kontext“Die Alternative zu absoluten Host-Pfaden lautet nicht einfach: „Immer relativ navigieren.“
Ein Befehl wie
router.navigate(['details']);ist nur dann eindeutig, wenn feststeht, relativ zu welcher aktivierten Route er aufgelöst wird.
Ohne einen definierten Kontext kann der Router den Befehl am Root einhängen:
Router-Root└── detailsErwartet wäre jedoch möglicherweise:
Remote-Mount└── tasks └── detailsIm Angular-Fall wurde deshalb die aktivierte Mount-Route des Remotes als technischer Kontext verwendet:
router.navigate(['details'], { relativeTo: remoteShellRoute,});Der zugrunde liegende Vertrag lässt sich vereinfacht so ausdrücken:
interface RemoteMountContext { baseRoute: ActivatedRoute;}Das Interface ist kein allgemeiner Microfrontend-Standard. ActivatedRoute ist eine konkrete Angular-Abhängigkeit und damit an die gemeinsame Angular-Laufzeit gebunden. Das Architekturprinzip dahinter ist jedoch frameworkneutral:
Das Remote benötigt keinen hartcodierten Host-Pfad. Es benötigt einen verlässlichen Kontext, innerhalb dessen seine relativen Routen gelten.
React Router kann dafür einen basename oder einen delegierten Routerbereich verwenden. Andere Integrationen können Base Paths, Mount Props oder eine Navigation-Abstraktion bereitstellen. Entscheidend ist nicht der Name der API, sondern die explizite Zuordnung des relativen Routenraums.
Relative Navigation ist ohne definierten Mount-Kontext nicht automatisch unabhängig. Sie ist lediglich unvollständig spezifiziert.

Navigation Intent ist nicht Router-Synchronisation
Abschnitt betitelt „Navigation Intent ist nicht Router-Synchronisation“Im Architektur-Lab wurde die Navigation nicht direkt aus jeder Komponente heraus ausgeführt. Stattdessen äußerten Komponenten kleine semantische Navigationsabsichten:
openListopenDetailsopenEditopenProjectDetailEin solcher Intent beschreibt, was die Anwendung anzeigen soll. Er enthält weder den Host-Präfix noch konkrete Angular Router Commands. Er muss keine ActivatedRoute kennen und sagt nichts über pushState, React Router oder Browser-History-APIs aus.
Der technische Adapter übersetzte den Intent anschließend in einen Router-Auftrag:
{ commands: ['details'], relativeTo: remoteShellRoute, replaceUrl: false}Ein Navigation Intent gehört zur fachlichen oder präsentationsnahen Bedeutung. Ein Router-Auftrag gehört zum technischen Adapter.
Im konkreten Fall lief diese Übersetzung über einen Angular-/NgRx-Signal-Eventkanal innerhalb derselben Angular-Anwendung. Es wurden keine window-Events, CustomEvent-Nachrichten oder postMessage-Aufrufe eingesetzt. Der Kanal war auch kein frameworkneutraler Vertrag zwischen unabhängigen Laufzeiten.
Er transportierte letztlich einen einzelnen Auftrag an denselben Angular Router.
Das Event synchronisierte keine Router. Es übersetzte einen Navigation Intent in einen relativen Router-Auftrag.
Diese Abgrenzung ist wichtig. Ein Event wird nicht dadurch zu einem stabilen Microfrontend-Vertrag, dass es global benannt ist. Entscheidend ist, welche Verantwortung es transportiert.
Ein kleiner produktweiter Intent kann beispielsweise so aussehen:
interface ProductNavigationIntent { target: ProductLocation; history?: 'push' | 'replace';}Er beschreibt ein Ziel und überlässt die konkrete URL dem zuständigen Adapter. Er verteilt keinen Router-Zustand, transportiert keinen fremden fachlichen Zustand und verlangt nicht, dass mehrere Stores oder Router miteinander abgeglichen werden.
Problematisch wäre dagegen eine Kette wie diese:
Router A ändert URL→ globales Event→ Router B gleicht Zustand ab→ Router B ändert URL→ weiteres EventDas wäre keine Navigation über eine klare Grenze, sondern Router-Synchronisation.
Navigation Intent└── Eine Komponente bittet den URL-Owner um Navigation.
Router-Synchronisation└── Mehrere Router versuchen, denselben Zustand gegenseitig abzugleichen.Navigation darf als Intent über eine Grenze gehen. Die kanonische Browser-History sollte trotzdem einen klaren mutierenden Owner besitzen.
Ein Router ist der einfache Fall
Abschnitt betitelt „Ein Router ist der einfache Fall“In der homogenen Angular-Integration des Architektur-Labs verwendete der Host einen Angular Router. Die Angular-Remotes exportierten relative Route-Arrays, die vom Host unter Produktpräfixen eingehängt wurden.
Das Ergebnis ließ sich vereinfacht so darstellen:
Browser-URL │ ▼Angular Router │ ├── /todos │ └── relative Todos-Routen │ ├── /tasks │ └── relative Tasks-Routen │ └── /members-directory └── relative Members-RoutenAlle Beteiligten verwendeten dieselbe Browser-History. Das Remote konnte eine Navigation anfordern, der gemeinsame Router erzeugte die kanonische URL und derselbe Router verarbeitete später Browser-Zurück.
Damit war die mutierende Ownership eindeutig:
Ein Router└── eine Browser-HistoryDas bedeutet nicht, dass die Remotes keine Route Ownership besitzen. Sie besitzen weiterhin ihre relativen Routenbäume und deren fachliche Bedeutung. Der gemeinsame Router ist lediglich der technische Mechanismus, der diese Bäume in einen kanonischen URL-Raum integriert.
Gerade diese Unterscheidung verhindert einen unnötigen Shell-Monolithen. Der Host muss nicht sämtliche internen Routen fachlich modellieren. Er stellt den Mount-Kontext bereit und setzt die relativen Routenbäume zusammen.
Zwei Router teilen dieselbe Browser-History
Abschnitt betitelt „Zwei Router teilen dieselbe Browser-History“Komplexer wird die Situation, wenn innerhalb eines Produktbereichs ein zweiter Router startet.
Im Architektur-Lab existierte dafür ein React Activity Stream. Der Angular Host Router beobachtete einen Bereich wie:
/activity-stream/...Innerhalb dieses Bereichs verwendete das React-Remote einen eigenen BrowserRouter:
<BrowserRouter basename="/activity-stream">{/* Remote-Routen */}</BrowserRouter>Damit beobachteten zwei Router denselben URL-Raum:
Browser-History├── Angular Host Router└── React BrowserRouterEin basename hilft dem React Router, seine relativen Pfade unterhalb des Produktpräfixes zu interpretieren. Es beantwortet jedoch noch nicht vollständig die Ownership-Frage:
Wer darf die kanonische Browser-URL verändern, und wer darf sie nur interpretieren?
Verschachtelte Router sind nicht automatisch falsch. Mehrere Modelle können funktionieren.
Delegierter URL-Bereich
Abschnitt betitelt „Delegierter URL-Bereich“Der Host erkennt lediglich den äußeren Präfix:
Host└── erkennt /activity-streamDas React-Remote besitzt alles darunter:
React-Remote└── besitzt den relativen URL-BereichIn diesem Modell darf das Remote die History innerhalb seines delegierten Bereichs verändern. Der Vertrag muss dafür Mounting, Unmounting, direkte Einstiege und popstate-Verhalten eindeutig regeln.
Host als einziger URL-Owner
Abschnitt betitelt „Host als einziger URL-Owner“Das React-Remote äußert nur semantische Navigationsziele. Ein Adapter der Produktshell erzeugt daraus die Browser-URL.
React-Remote└── äußert Navigation Intent
Host└── verändert Browser-URLDieses Modell reduziert konkurrierende History-Zugriffe, koppelt das Remote aber stärker an den bereitgestellten Navigation Port.
Geteilte History ohne expliziten Vertrag
Abschnitt betitelt „Geteilte History ohne expliziten Vertrag“Beide Router verändern und beobachten dieselbe Browser-History:
Angular Router+React Router└── verändern und beobachten dieselbe HistoryDieses Modell kann eine Zeit lang funktionieren. Riskant wird es, sobald pushState, replaceState, popstate, Mounting und Unmounting nicht mehr dieselbe Vorstellung vom aktiven URL-Bereich besitzen.
Mehrere Router sind beherrschbar. Mehrere unklare Besitzer derselben History sind es deutlich weniger.

Wer darf die kanonische URL verändern?
Abschnitt betitelt „Wer darf die kanonische URL verändern?“Die Frage nach dem mutierenden Owner lässt sich nicht für jede Architektur gleich beantworten.
Bei einem gemeinsamen Router liegt die technische Verantwortung meist ohnehin bei diesem Router. Bei einem delegierten URL-Bereich kann ein Remote die Browser-History innerhalb seiner Grenze selbst verändern. Über eine Frameworkgrenze hinweg kann ein Navigation Port sinnvoller sein.
Wichtig ist, dass ein konkretes Modell gewählt wird.
Ein Remote darf innerhalb seines delegierten Routenraums technisch navigieren. Bei Navigation zu einem anderen Produktbereich sollte es dagegen nicht zwangsläufig dessen URL-Struktur hartcodieren.
Problematisch wäre beispielsweise:
navigate('/members-directory/details/42');Das aufrufende Remote kennt nun den Namen, den Mount-Pfad und die interne Struktur eines fremden Produktbereichs.
Ein stabilerer produktweiter Intent könnte lauten:
navigation.openProductArea({ area: 'members', target: 'detail', id: '42',});Der Plattformadapter erzeugt daraus die kanonische Produkt-URL.
Daraus darf allerdings kein zentraler fachlicher Megarouter entstehen, der sämtliche internen Routen aller Remotes katalogisiert. Der Vertrag sollte nur jene Navigation abbilden, die tatsächlich über eine Ownership-Grenze führt.
Innerhalb seines delegierten Routenraums darf ein Remote technisch navigieren. Über Produktgrenzen hinweg sollte es ein Ziel beschreiben, nicht die fremde URL-Struktur besitzen.
Route Ownership endet außerdem nicht am Pfadsegment. Auch weitere Bestandteile der URL und der History benötigen eine definierte Verantwortung:
- Pfad,
- Query-Parameter,
- Fragment,
- History-Modus
pushoderreplace, - optionaler Navigation State.
Wer besitzt Filterparameter? Bleiben sie bei interner Navigation erhalten? Gehört ein Fragment zum Remote oder zur Shell? Soll ein technischer Redirect den aktuellen History-Eintrag ersetzen? Werden Parameter zwischen Standalone- und Host-Modus korrekt abgebildet?
Route Ownership endet nicht am Pfadsegment. Auch Query, Fragment und History-Semantik benötigen eine definierte Verantwortung.
Browser-Zurück rekonstruiert die Route
Abschnitt betitelt „Browser-Zurück rekonstruiert die Route“Eine ausgelöste Navigation und Browser-Zurück sind zwei unterschiedliche Vorgänge.
Bei einer ausgelösten Navigation entsteht ein neuer Zustand:
Benutzer klickt auf „Details“→ Anwendung äußert Navigation Intent→ URL-Owner erzeugt History-EintragBei Browser-Zurück aktiviert der Browser dagegen einen bereits vorhandenen Eintrag:
Benutzer betätigt Zurück→ Browser aktiviert bestehenden History-Eintrag→ Router interpretiert neue URLNavigation erzeugt History. Browser-Zurück interpretiert bereits vorhandene History.
Bei einem gemeinsamen Angular Router verarbeitet dieser Router das popstate-Signal, erkennt die URL und aktiviert den passenden Routenbaum erneut. Dafür sollte nicht erst ein fachlicher Navigation Intent rekonstruiert werden müssen.
Wer jede beobachtete URL-Änderung erneut als Navigation Event veröffentlicht, riskiert doppelte History-Einträge oder Rückkopplungsschleifen. Der Router interpretiert dann einen bestehenden Eintrag, woraufhin die Anwendung denselben Zustand noch einmal als neue Navigation erzeugt.
Browser-Zurück ist deshalb kein Navigation Intent. Es ist eine externe Zustandsänderung, die der URL-Owner und die beteiligten Routenbäume interpretieren müssen.
Eine Route rekonstruiert nicht automatisch den View-Zustand
Abschnitt betitelt „Eine Route rekonstruiert nicht automatisch den View-Zustand“Im Architektur-Lab ließ sich diese Grenze an einem typischen Ablauf beobachten:
- Der Host öffnet
/todos/board. - Der Benutzer wählt ein Todo.
- Der lokale Store speichert die Auswahl.
- Die Anwendung navigiert nach
/todos/details. - Der Benutzer wechselt zu einem anderen Remote.
- Das Todos-Remote wird deaktiviert.
- Der Benutzer betätigt Browser-Zurück.
/todos/detailswird erneut aktiviert.- Der neu erzeugte Store kennt das zuvor ausgewählte Todo nicht mehr.
Der Router hatte seine Aufgabe erfüllt:
URL wiederhergestelltRoute erkanntRemote gemountetDie fachliche Detailansicht war dennoch nicht rekonstruierbar. Die Route adressierte lediglich „Details“, nicht aber das konkrete Todo. Die notwendige Auswahl hatte ausschließlich im inzwischen verworfenen Store existiert.
Daraus ergibt sich eine wichtige Abstufung:
Dokument ist ladbar≠Route ist auflösbar≠Ansicht ist rekonstruierbar≠fachlicher Zustand ist wiederhergestelltEine wiederhergestellte Route ist nicht dasselbe wie ein wiederhergestellter View-Zustand.
Die Browser-History kann nur den Zustand wiederherstellen, der in der URL oder über einen daraus ableitbaren Vertrag adressierbar ist.
Die Verantwortung dafür liegt nicht automatisch beim Host. Das Remote besitzt die fachliche Bedeutung seiner Route und muss entscheiden, welche Referenzen für eine Rekonstruktion benötigt werden. Der Host kann den Bereich aktivieren, aber er sollte nicht die lokale Entity-Auswahl des Remotes erraten müssen.
Ein SPA-Fallback ist noch kein Deep Link
Abschnitt betitelt „Ein SPA-Fallback ist noch kein Deep Link“Bei einem direkten Browseraufruf wie
https://product.example/todos/detailsmüssen mehrere Ebenen funktionieren:
- DNS und Reverse Proxy wählen den richtigen Dienst.
- Der Webserver liefert das Einstiegspunkt-Dokument.
- Die Shell erkennt den Remote-Präfix.
- Das Remote erkennt seinen relativen Unterpfad.
- Benötigte fachliche Daten werden rekonstruiert.
- Authentifizierungs-Redirects erhalten das ursprüngliche Ziel.
Ein typischer Webserver-Fallback kann sicherstellen, dass auch bei einem unbekannten Dateipfad das SPA-Dokument ausgeliefert wird:
try_files $uri $uri/ /index.html;Damit ist bewiesen, dass der Browser ein Einstiegspunkt-Dokument erhält. Mehr nicht.
Der Fallback beweist weder ein erfolgreiches Remote-Mounting noch die Auflösung des relativen Pfads. Er sagt nichts über Berechtigungen, fachliche Daten oder die Wiederherstellung einer ausgewählten Entity aus. Auch ein Login-Redirect kann das Ziel verlieren, obwohl der Webserver vorher korrekt index.html ausgeliefert hat.
Ein Server-Fallback macht eine URL ladbar, aber noch nicht fachlich rekonstruierbar.
Eine auflösbare Route ist noch kein belastbarer Deep Link. Eine URL wird erst dann rekonstruierbar, wenn sie den Zustand adressiert, den die Anwendung nach Reload, Remount oder Browser-Zurück benötigt.

Muss jede Detailansicht eine Entity adressieren?
Abschnitt betitelt „Muss jede Detailansicht eine Entity adressieren?“Eine Route wie
/todos/detailskann funktionieren, wenn die konkrete Auswahl aus einem anderen stabilen Zustand rekonstruierbar ist. Innerhalb eines ununterbrochenen Arbeitsablaufs kann ein lokaler Store dafür ausreichen.
Nach einem Reload, einem Remount oder einem direkten externen Link fehlt diese Auswahl jedoch möglicherweise. Eine rekonstruierbare Alternative könnte deshalb lauten:
/todos/42oder:
/todos/details/42Daraus folgt nicht, dass jede Auswahl und jeder UI-Zustand in die URL geschrieben werden muss.
Ein belastbarer Deep Link soll typischerweise geteilt oder gebookmarkt werden können. Er soll nach Reload, Login-Redirect oder erneutem Mounting funktionieren. Dafür benötigt er eine stabile fachliche Referenz oder einen anderen reproduzierbaren Vertrag.
Ein transienter View-Zustand entsteht dagegen möglicherweise nur innerhalb eines laufenden Ablaufs. Er muss nicht bookmarkfähig sein, darf bei Reload verworfen werden und muss nicht künstlich zu einem öffentlichen Produktzustand erklärt werden.
Nicht jede Ansicht braucht eine eigene rekonstruierbare URL. Aber jede URL sollte ehrlich darüber sein, welchen Zustand sie tatsächlich wiederherstellen kann.
Eine Route ohne Entity-ID ist daher nicht automatisch fehlerhaft. Problematisch wird sie erst, wenn sie wie ein belastbarer Deep Link behandelt wird, obwohl ihre fachliche Voraussetzung ausschließlich in flüchtigem Speicher existiert.
Standalone- und Host-URLs dürfen unterschiedlich sein
Abschnitt betitelt „Standalone- und Host-URLs dürfen unterschiedlich sein“Die beiden Betriebsarten eines Remotes besitzen unterschiedliche Composition-Kontexte.
Standalone kann eine fachliche Route unter folgender URL erreichbar sein:
https://tasks.example/projects/detailsIm Produkt kann dieselbe Route so aussehen:
https://product.example/tasks/projects/detailsOrigin und äußerer Präfix unterscheiden sich. Die fachliche Route projects/details bleibt dennoch dieselbe.
Das ist kein Fehler. Ein Remote muss nicht in jedem Betriebsmodus dieselbe vollständige Browser-URL besitzen. Entscheidend ist, dass das Mapping explizit und überprüfbar ist.
Dafür sollten mindestens folgende Eigenschaften gelten:
- Der relative Routenraum bleibt stabil.
- Der Mount-Kontext ist explizit.
- Interne Navigation verwendet keine hartcodierten Host-Präfixe.
- Authentifizierungs-Redirects kennen die jeweilige kanonische URL.
- Dokument-Fallbacks funktionieren pro Origin.
- Tests prüfen sowohl den Standalone- als auch den Host-Betrieb.
Unabhängige Ausführbarkeit bedeutet nicht identische äußere URLs. Sie bedeutet, dass die fachliche Route in jedem Kontext eindeutig aufgelöst werden kann.
Der Standalone-Modus darf dabei ein eigenes Mapping bereitstellen. Ein Mini-Host oder eine lokale Shell kann denselben relativen Routenraum unter einem anderen äußeren Präfix aktivieren. Entscheidend ist, dass dieser Kontext nicht heimlich in die Remote-Fachlichkeit einfließt.
Route Ownership als Matrix
Abschnitt betitelt „Route Ownership als Matrix“Die Verantwortlichkeiten lassen sich als vereinfachte Matrix darstellen:
| Verantwortung | Host | Remote |
|---|---|---|
| Produkt-Origin im Host-Betrieb | besitzt | verwendet |
| Äußerer Mount-Präfix | besitzt | darf ihn nicht hartcodieren |
| Relativer Unterpfad | mountet | definiert |
| Fachliche Bedeutung der Route | kennt sie nur soweit nötig | besitzt |
| Produktweite Navigation | koordiniert | äußert Intent |
| Interne Remote-Navigation | stellt Kontext bereit | besitzt |
| Browser-History | stellt einen klaren Owner oder delegierten Bereich sicher | fordert Änderung an oder besitzt den delegierten Bereich |
| Deep-Link-Daten | aktiviert den Produktbereich | rekonstruiert den fachlichen Zustand |
| Server-Fallback | verantwortet den Host-Origin | verantwortet den Standalone-Origin |
Diese Aufteilung ist kein unveränderliches Gesetz. Sie macht jedoch sichtbar, wo unterschiedliche Verantwortungen häufig miteinander verwechselt werden.
Der Host kann den äußeren Pfad besitzen, ohne die fachliche Bedeutung jeder Unterroute zu kontrollieren. Das Remote kann seinen Routenraum besitzen, ohne den vollständigen Produktpfad zu kennen. Ein gemeinsamer Router kann die History verändern, ohne automatisch Eigentümer des fachlichen URL-Zustands zu sein.
Technische Pfad-Ownership und fachliche Zustands-Ownership sind nicht dasselbe.
Keine universelle Routingregel
Abschnitt betitelt „Keine universelle Routingregel“Je nach Integration können unterschiedliche Modelle tragfähig sein:
- Gemeinsamer Router: Der Host mountet relative Route-Arrays; ein Router besitzt die Browser-History.
- Delegierter Routerbereich: Der Host erkennt den äußeren Präfix; das Remote besitzt den URL-Raum darunter.
- Navigation Port: Das Remote beschreibt ein semantisches Ziel; ein Plattformadapter erzeugt die kanonische URL.
- Getrennte Standalone- und Host-Mappings: Der fachliche Zielraum bleibt stabil, während der Composition-Kontext unterschiedliche äußere URLs erzeugt.
Keines dieser Modelle ist pauschal richtig. Entscheidend ist, ob die Ownership eindeutig bleibt, direkte Einstiege und Browser-Zurück funktionieren, der eigenständige Betrieb realistisch testbar ist und der benötigte fachliche Zustand rekonstruiert werden kann.
Die Wahl des Modells ist weniger wichtig als die Klarheit seines Vertrags.
Die URL als stabiler Produktvertrag
Abschnitt betitelt „Die URL als stabiler Produktvertrag“Das Routingproblem aus dem Architektur-Lab ließ sich technisch mit relativeTo lösen. Seine architektonische Bedeutung lag jedoch nicht in dieser einzelnen Router-Option, sondern in der Trennung zweier Verantwortungen.
Der Host konnte /tasks als äußeren Produktbereich besitzen. Das Remote konnte details als eigene relative Route definieren. Der Mount-Kontext verband beide Teile, ohne den Host-Präfix in die Remote-Implementierung zu schreiben.
Ein hartcodierter Host-Präfix löst ein lokales Routingproblem und erzeugt eine strukturelle Abhängigkeit.
Navigation über eine Ownership-Grenze kann als Intent formuliert werden. Die konkrete URL erzeugt anschließend der zuständige Adapter, der gemeinsame Router oder ein klar delegierter Routerbereich. Mehrere Router bleiben möglich, benötigen aber einen eindeutigen mutierenden History-Owner oder explizit delegierte URL-Bereiche.
Damit endet die Verantwortung noch nicht. Eine Route kann gültig sein, obwohl der fachliche Zustand fehlt. Ein Server-Fallback kann das SPA-Dokument ausliefern, obwohl die benötigte Entity nicht rekonstruierbar ist.
Eine wiederhergestellte Route ist nicht dasselbe wie ein wiederhergestellter fachlicher Zustand.
Standalone- und Host-Modus dürfen unterschiedliche äußere URLs besitzen. Entscheidend ist ein explizites Mapping zwischen Composition-Kontext und relativem Routenraum.
Ein stabiler Routingvertrag beantwortet deshalb mehr als die Frage, wie eine Komponente von create nach details gelangt. Er beantwortet, wer den äußeren Produktpfad besitzt, wer den relativen Unterpfad interpretiert, wer History-Einträge erzeugt und welcher fachliche Zustand nach Reload, Remount oder Browser-Zurück wiederhergestellt werden kann.
Die URL gehört nicht dem Router, der sie gerade verändert. Sie gehört dem Produktvertrag, der sie wiederherstellbar macht.