skip to content

In an Angular app, a route never renders or a redirect keeps looping; how do you diagnose the route configuration?

level: seniorimportance: should knowfreq 40%

answer

  1. read the dev-mode config errors
  2. look for shadowing earlier routes
  3. 'Cannot match any routes' names the segment
  4. absolute redirects restart matching
  5. trace router events while navigating

basics

~20 s

Check the console for the router's dev-mode configuration errors, look for earlier routes shadowing the missing one (a ** or :param too high), check pathMatch on parents and redirects, and trace navigations with withDebugTracing() to see which route matched or why matching failed.

solid answer

~50 s

First the console: in development the router validates its configuration when it receives it and throws precise errors — a path starting with a slash, an `undefined` entry from a stray comma, `redirectTo` combined with `component` or `canActivate`, an empty-path redirect without `pathMatch`. At navigation time, `NG04002` "Cannot match any routes. URL Segment: ..." names the segment nothing matched, and `NG04016` flags a possible infinite loop of absolute redirects. Then read the table in order: first match wins, so a `**` or `:slug` route above the missing one shadows it, and `pathMatch: 'full'` on a parent blocks its children. Relative redirects are not re-redirected at the same level, so a chain of two relative redirects ends in no match. Finally, `provideRouter(routes, withDebugTracing())` logs every router event, showing which configuration was recognised or where navigation errored.

code

ts · 8 lines
ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withDebugTracing } from '@angular/router';
import { routes } from './app.routes';

// Temporary: logs every router event (NavigationStart, RoutesRecognized, NavigationError, ...)
export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes, withDebugTracing())],
};

go deeper

for a junior

Recall that route order matters, paths have no leading slash, and the ** route belongs last.

for a middle

Explain the dev-mode configuration errors and the NG04002 and NG04016 navigation errors, and what each one points to.

for a senior

Diagnose shadowing, parent pathMatch mistakes and redirect chains systematically, confirm with router tracing, and lock the fix in with a route test.

for a principal

Establish route-table conventions and tests so large teams can add routes without shadowing each other's URLs.

## Start with what the router already tells you In **development builds** Angular's router validates the route configuration when it receives it and throws `NG04014` errors with a readable message. The common ones: - "path cannot start with a slash" — paths are relative: `'posts'`, not `'/posts'`. - "Encountered undefined route. The reason might be an extra comma" — `,,` left an `undefined` hole in the array. - "redirectTo and component/loadComponent cannot be used together" — a route either renders or redirects. - "redirectTo and canActivate cannot be used together. Redirects happen before guards are executed." - "please provide 'pathMatch'" — an empty-path redirect without `pathMatch`. - "One of the following must be provided: component, loadComponent, redirectTo, children or loadChildren". These checks are skipped in production builds, so reproduce configuration bugs in a development build before anything else. ## Errors at navigation time | Symptom | Likely cause | |---|---| | `NG04002` "Cannot match any routes. URL Segment: '...'" | No route (and no `**`) consumes that segment; or a chained relative redirect left segments nothing matches | | `NG04016` "Detected possible infinite redirect" (dev) | Absolute string redirects bouncing between URLs; reported after the router's redirect limit | | `NG04001` "Cannot redirect to '...'. Cannot find ':x'" | A `redirectTo` string uses a placeholder the redirect route's path does not define | | The wrong component renders, no error | An earlier route matches first | | The 404 page renders for a valid URL | The `**` route is not last, or the intended route cannot consume the whole URL | ## Reading the table like the router does 1. **Order.** The router walks each routes array top to bottom and takes the first route that matches. Look above the missing route for `**`, a parameter route of the same shape (`posts/:slug` above `posts/new`), or an empty-path route with children that captures everything. 2. **Whole-URL rule.** A route without children must consume all remaining segments. `posts/:slug` does not match `/posts/2026/hello`; a `:param` is one segment. 3. **`pathMatch` on parents.** `'full'` on a route with children means it only matches when nothing follows, so its children never see `/posts/hello`. 4. **Redirect semantics.** A relative redirect replaces the consumed segments and re-matches at the same level with redirects **disabled**, so `a -> b` followed by `b -> c` at one level does not reach `c`; that navigation fails to match. An absolute redirect (leading slash, or a `UrlTree` from a function) restarts matching from the root, which is how loops arise. 5. **Backtracking.** When a parent matches but none of its children can consume the rest, the router abandons that parent and tries the next sibling — a component route may silently lose to a later one. ## Tracing a navigation `provideRouter(routes, withDebugTracing())` logs every router event to the console. For a failing navigation you see where it stops: a `NavigationError` with the matching error, a `NavigationCancel` from a guard, or a `RoutesRecognized` whose state shows which configuration was actually chosen. The feature only logs in development builds — in production it provides nothing — but remove it once the bug is found, because the event noise buries other console output. ## A worked diagnosis A team adds `{ path: 'posts/new', component: NewPost }` at the end of the routes, after `posts/:slug` and `**`. `/posts/new` renders `PostDetail` with `slug = 'new'`, and nothing errors. The tracing output shows `RoutesRecognized` with the `posts/:slug` configuration. The fix is to move `posts/new` above `posts/:slug`, and to keep `**` as the final entry. ## Prevent it next time - Keep a **route-table test**: navigate the router to representative URLs and assert which component renders. - Order each array: empty-path redirects, static paths, parameter paths, then `**`. - Use `pathMatch: 'full'` on empty-path and exact-URL redirects only. - Prefer relative paths and one redirect hop; chained redirects are hard to reason about.

  • Why might a route configuration error appear locally but not in production?
    The router's configuration validation runs only in development builds. Production skips it, so a bad table does not throw at startup; it simply fails to match or behaves oddly at navigation time. Reproduce routing bugs in a development build to get the precise messages.
  • How would you stop route-order regressions from coming back?
    Add a small test that navigates the router to key URLs — `/`, `/posts`, `/posts/new`, `/posts/some-slug`, an unknown URL — and asserts which component renders. A reordered or newly added route that shadows another then fails the test instead of reaching users.

saying these in an interview costs you the question

  • The router validates route configuration in production builds too
  • A relative redirect can chain into another redirect at the same level
  • Cannot match any routes always means the route is missing
  • Absolute and relative redirects restart matching the same way
  • withDebugTracing logs router events in production builds too