Skip to content

feat(router): add trailingSlash config option#66276

Merged
thePunderWoman merged 2 commits intoangular:mainfrom
atscott:trailingslashbackup
Jan 8, 2026
Merged

feat(router): add trailingSlash config option#66276
thePunderWoman merged 2 commits intoangular:mainfrom
atscott:trailingslashbackup

Conversation

@atscott
Copy link
Copy Markdown
Contributor

@atscott atscott commented Dec 29, 2025

This commit introduces a highly requested trailingSlash configuration option to the Angular Router, allowing developers to control how trailing slashes are handled in their applications. The options are:

  • 'always': Enforces a trailing slash on all URLs.
  • 'never': Removes trailing slashes from all URLs (default).
  • 'preserve': Respects the presence or absence of a trailing slash as defined in the UrlTree.

Additionally, this commit updates the defaultUrlMatcher to optionally consume a single trailing empty path segment if the route otherwise matches. This ensures that routes with pathMatch: 'full' can correctly match URLs with trailing slashes (e.g., /a/ matching {path: 'a', pathMatch: 'full'}), resolving a long-standing inconsistency where such routes would fail despite pathMatch: 'prefix' variants (with empty children) succeeding.

Alternatives Considered & Design Rationale:

  • UrlTree.hasTrailingSlash Property: We initially explored adding a explicit boolean property to UrlTree. However, this would have required a significant public API change and breaking updates to custom UrlSerializer implementations to ensure the state was preserved. It also complicated the mental model of the UrlTree as a direct representation of the URL structure. We settled on representing trailing slashes as a trailing empty UrlSegment, which leverages the existing structure and serialization logic without API expansion.

    Why we chose the current approach:
    We decided on a "Permissive Matcher, Strict Serializer/Location" pattern.

    • The Matcher is permissive: it allows consuming a trailing empty segment if it's the only thing left, treating it effectively as optional for pathMatch: 'full'. This aligns logic with how pathMatch: 'prefix' matches empty children.
    • The Serializer/Location are strict: The trailingSlash configuration is verified at the boundaries (serialization and location normalization). If trailingSlash: 'never' is set, the slash is stripped before matching ever occurs. If matching sees a slash, it implies the configuration allowed it (or we are in a permissive mode), so the matcher should handle it gracefully.

fixes #16051

@atscott atscott added the target: minor This PR is targeted for the next minor release label Dec 29, 2025
@angular-robot angular-robot bot added detected: feature PR contains a feature commit area: router labels Dec 29, 2025
@atscott atscott added area: common Issues related to APIs in the @angular/common package and removed detected: feature PR contains a feature commit labels Dec 29, 2025
@ngbot ngbot bot added this to the Backlog milestone Dec 29, 2025
@atscott atscott force-pushed the trailingslashbackup branch from 57b5dad to 0472af6 Compare January 5, 2026 22:49
@angular-robot angular-robot bot added the detected: feature PR contains a feature commit label Jan 5, 2026
@atscott atscott force-pushed the trailingslashbackup branch 5 times, most recently from 00f336c to 9490c7e Compare January 5, 2026 23:27
@atscott atscott force-pushed the trailingslashbackup branch 2 times, most recently from 24c2aee to 949b72e Compare January 6, 2026 18:56
@atscott atscott marked this pull request as ready for review January 6, 2026 18:59
@pullapprove pullapprove bot requested review from JeanMeche and kirjs January 6, 2026 18:59
@atscott atscott force-pushed the trailingslashbackup branch from 949b72e to 308fd49 Compare January 6, 2026 20:35
@pullapprove pullapprove bot requested a review from JeanMeche January 6, 2026 20:43
@pullapprove pullapprove bot requested a review from JeanMeche January 6, 2026 20:58
Copy link
Copy Markdown
Member

@JeanMeche JeanMeche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed-for: public-api

@atscott atscott removed the request for review from kirjs January 6, 2026 21:39
@atscott atscott added the action: merge The PR is ready for merge by the caretaker label Jan 6, 2026
@JeanMeche JeanMeche force-pushed the trailingslashbackup branch from 49a133a to 1dd866a Compare January 7, 2026 14:54
@JeanMeche
Copy link
Copy Markdown
Member

JeanMeche commented Jan 7, 2026

(just pushed a rebase to solve the conflict and started the presubmit)

@atscott atscott force-pushed the trailingslashbackup branch from 1dd866a to 77c5e76 Compare January 7, 2026 20:39
@atscott atscott removed the action: merge The PR is ready for merge by the caretaker label Jan 7, 2026
This commit introduces a highly requested `trailingSlash` configuration option to the Angular Router, allowing developers to control how trailing slashes are handled in their applications. The options are:
- 'always': Enforces a trailing slash on all URLs.
- 'never': Removes trailing slashes from all URLs (default).
- 'preserve': Respects the presence or absence of a trailing slash as defined in the UrlTree.

Additionally, this commit updates the `defaultUrlMatcher` to optionally consume a single trailing empty path segment if the route otherwise matches. This ensures that routes with `pathMatch: 'full'` can correctly match URLs with trailing slashes (e.g., `/a/` matching `{path: 'a', pathMatch: 'full'}`), resolving a long-standing inconsistency where such routes would fail despite `pathMatch: 'prefix'` variants (with empty children) succeeding.

**Alternatives Considered & Design Rationale:**

*  **`UrlTree.hasTrailingSlash` Property:**
    We initially explored adding a explicit boolean property to `UrlTree`.
    However, this would have required a significant public API change and breaking updates to custom `UrlSerializer` implementations to ensure the state was preserved. It also complicated the mental model of the `UrlTree` as a direct representation of the URL structure. We settled on representing trailing slashes as a trailing empty `UrlSegment`, which leverages the existing structure and serialization logic without API expansion.

    **Why we chose the current approach:**
    We decided on a "Permissive Matcher, Strict Serializer/Location" pattern.
    -   The **Matcher** is permissive: it allows consuming a trailing empty segment if it's the *only* thing left, treating it effectively as optional for `pathMatch: 'full'`. This aligns logic with how `pathMatch: 'prefix'` matches empty children.
    -   The **Serializer/Location** are strict: The `trailingSlash` configuration is verified at the boundaries (serialization and location normalization). If `trailingSlash: 'never'` is set, the slash is stripped *before* matching ever occurs. If matching sees a slash, it implies the configuration allowed it (or we are in a permissive mode), so the matcher should handle it gracefully.

fixes angular#16051
@atscott atscott force-pushed the trailingslashbackup branch from 77c5e76 to 072bae4 Compare January 7, 2026 20:41
@JeanMeche JeanMeche added the action: merge The PR is ready for merge by the caretaker label Jan 8, 2026
@thePunderWoman thePunderWoman merged commit 12fccc5 into angular:main Jan 8, 2026
20 checks passed
@thePunderWoman
Copy link
Copy Markdown
Contributor

This PR was merged into the repository. The changes were merged into the following branches:

@martinsotirov
Copy link
Copy Markdown

Better late than never!

@thePunderWoman
Copy link
Copy Markdown
Contributor

We unfortunately had had to revert this change due to breakages internally.

@angular-automatic-lock-bot
Copy link
Copy Markdown

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Feb 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: merge The PR is ready for merge by the caretaker area: common Issues related to APIs in the @angular/common package area: router detected: feature PR contains a feature commit target: minor This PR is targeted for the next minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Routing: Preserve trailing slash

5 participants