fix(portal-shell): catch-all route prevents NG04002 on /fr in dev mode #96

Merged
julien merged 1 commits from fix/portal-shell/router-wildcard-fallback into main 2026-05-11 22:12:25 +02:00
Owner

Reproducer

  1. pnpm exec nx serve portal-shell (source-locale dev server, no --localize).
  2. Open http://localhost:4200/, the EN home page renders.
  3. Click the locale switcher's Français entry in the footer.

Browser navigates to /fr/. The Angular CLI dev server applies its SPA fallback and serves the same source index.html. The source bundle boots with <base href="/">, the router tries to match the URL /fr/, finds no route, and rejects the bootstrap promise:

ERROR RuntimeError: NG04002: Cannot match any routes. URL Segment: 'fr'

Fix

Add a { path: '**', redirectTo: '' } catch-all to app.routes.ts. Unknown paths now bounce to home gracefully.

Why this doesn't break production

Production builds with --localize emit one bundle per locale, each with its own <base href="/{locale}/">. The router never sees the locale segment — /fr/foo is normalised to foo before route matching, hitting the bundle's foo route. The wildcard only fires when no declared route matches, which is exactly what we want for genuine 404 paths in either mode.

What this does NOT fix

The locale switcher's underlying dev-mode limitation stands: under nx serve there is no per-locale bundle, so switching to FR from dev mode can only land back on the source-locale bundle. The fix turns the ugly bootstrap error into a silent bounce to home, so the dev iteration is no longer interrupted — but full locale switching still requires the production build (nx run portal-shell:serve-static or a real deploy). This matches the limitation already documented in PR #95.

Test plan

  • pnpm exec nx run-many -t lint test build --projects=portal-shell — green (40 / 40 specs unchanged).
  • Manual nx serve + click Français → URL becomes /fr/, page bounces back to / with no console error.
  • Manual prod build + serve-static, /fr/ → loads FR bundle as before, no regression.
  • Manual prod build + serve-static, /fr/does-not-exist → router-level 404 catch redirects to /fr/ (home), no NG04002.
## Reproducer 1. `pnpm exec nx serve portal-shell` (source-locale dev server, no `--localize`). 2. Open `http://localhost:4200/`, the EN home page renders. 3. Click the locale switcher's **Français** entry in the footer. Browser navigates to `/fr/`. The Angular CLI dev server applies its SPA fallback and serves the same source `index.html`. The source bundle boots with `<base href="/">`, the router tries to match the URL `/fr/`, finds no route, and rejects the bootstrap promise: ``` ERROR RuntimeError: NG04002: Cannot match any routes. URL Segment: 'fr' ``` ## Fix Add a `{ path: '**', redirectTo: '' }` catch-all to [`app.routes.ts`](apps/portal-shell/src/app/app.routes.ts). Unknown paths now bounce to home gracefully. ## Why this doesn't break production Production builds with `--localize` emit one bundle per locale, each with its own `<base href="/{locale}/">`. The router never sees the locale segment — `/fr/foo` is normalised to `foo` before route matching, hitting the bundle's `foo` route. The wildcard only fires when **no** declared route matches, which is exactly what we want for genuine 404 paths in either mode. ## What this does NOT fix The locale switcher's underlying dev-mode limitation stands: under `nx serve` there is no per-locale bundle, so switching to FR from dev mode can only land back on the source-locale bundle. The fix turns the ugly bootstrap error into a silent bounce to home, so the dev iteration is no longer interrupted — but full locale switching still requires the production build (`nx run portal-shell:serve-static` or a real deploy). This matches the limitation already documented in PR #95. ## Test plan - [x] `pnpm exec nx run-many -t lint test build --projects=portal-shell` — green (40 / 40 specs unchanged). - [ ] Manual `nx serve` + click Français → URL becomes `/fr/`, page bounces back to `/` with no console error. - [ ] Manual prod build + serve-static, `/fr/` → loads FR bundle as before, no regression. - [ ] Manual prod build + serve-static, `/fr/does-not-exist` → router-level 404 catch redirects to `/fr/` (home), no NG04002.
julien added 1 commit 2026-05-11 22:12:07 +02:00
fix(portal-shell): catch-all route prevents NG04002 on /fr in dev mode
CI / scan (pull_request) Successful in 3m0s
CI / check (pull_request) Successful in 3m20s
CI / commits (pull_request) Successful in 3m18s
CI / a11y (pull_request) Successful in 2m2s
CI / perf (pull_request) Successful in 5m45s
0e0419ec5e
Reproducer: `nx serve portal-shell` (source-locale dev server, no
`--localize`), open the home page, click the locale switcher's
"Français" entry. The switcher rewrites the URL to `/fr/` and
hard-refreshes; the Angular CLI dev server applies its SPA fallback
and serves the same source `index.html` for every path. The source
bundle boots with `<base href="/">`, the router tries to match
`/fr/` and finds no route → `NG04002: Cannot match any routes.
URL Segment: 'fr'` in the bootstrap promise rejection.

Fix: add a `{ path: '**', redirectTo: '' }` catch-all so unknown
paths bounce to home gracefully. In production this is a no-op
because each locale ships with its own `<base href="/{locale}/">`
and the locale segment is stripped before route matching — `/fr/foo`
hits the bundle's `foo` route, not the wildcard. The route also
serves as a defensive 404-to-home for any future bad URL in prod.

The locale switcher's actual limitation in dev (no per-locale
bundle, switching can only land on the source locale) stands; this
just turns the ugly error into a silent bounce so the dev iteration
is not interrupted.
julien merged commit d118d09aba into main 2026-05-11 22:12:25 +02:00
julien deleted branch fix/portal-shell/router-wildcard-fallback 2026-05-11 22:12:28 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: julien/apf_portal#96