Skip to content

fix(react-router): prevent incorrectly matching paths without slash separator#14689

Merged
brophdawg11 merged 5 commits intoremix-run:devfrom
nwleedev:feature/match-path-regex-pattern-fix
Jan 21, 2026
Merged

fix(react-router): prevent incorrectly matching paths without slash separator#14689
brophdawg11 merged 5 commits intoremix-run:devfrom
nwleedev:feature/match-path-regex-pattern-fix

Conversation

@nwleedev
Copy link
Copy Markdown
Contributor

Problem

Optional dynamic segments (:param?) incorrectly match paths that extend the base path without a separator. For example:

matchPath('/test_route/:part?', '/test_route_more')
// Expected: null
// Actual: { params: { part: '_more' } }

The current regex pattern /?([^\/]+)? makes the slash and capture group independently optional.
It allows /test_route_more to match as /test_route + _more without a preceding slash.

Changes

The regex pattern to match paths

  • Groups the slash with the capture
  • Ensures slash and parameter appear together or not at all
  • Uses * instead of + to allow empty captures for backward compatibility
    (e.g., /sitemap/:lang?.xml matching /sitemap/.xml)

Impact

  • No breaking changes
    • Only fixes incorrect matching behavior
  • Maintains backward compatibility with existing patterns

Fixes #14640

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Dec 27, 2025

🦋 Changeset detected

Latest commit: e3c81b0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
react-router Patch
@react-router/architect Patch
@react-router/cloudflare Patch
@react-router/dev Patch
react-router-dom Patch
@react-router/express Patch
@react-router/node Patch
@react-router/serve Patch
@react-router/fs-routes Patch
@react-router/remix-routes-option-adapter Patch
create-react-router Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@remix-cla-bot
Copy link
Copy Markdown
Contributor

remix-cla-bot Bot commented Dec 27, 2025

Hi @nwleedev,

Welcome, and thank you for contributing to React Router!

Before we consider your pull request, we ask that you sign our Contributor License Agreement (CLA). We require this only once.

You may review the CLA and sign it by adding your name to contributors.yml.

Once the CLA is signed, the CLA Signed label will be added to the pull request.

If you have already signed the CLA and received this response in error, or if you have any questions, please contact us at hello@remix.run.

Thanks!

- The Remix team

@remix-cla-bot
Copy link
Copy Markdown
Contributor

remix-cla-bot Bot commented Dec 27, 2025

Thank you for signing the Contributor License Agreement. Let's get this merged! 🥳

@brophdawg11
Copy link
Copy Markdown
Contributor

Would you mind adding a changeset via pnpm changeset?

@nwleedev
Copy link
Copy Markdown
Contributor Author

nwleedev commented Jan 5, 2026

Would you mind adding a changeset via pnpm changeset?

Apologies for missing the changeset request. I have created a patch changeset for react-router.

(_: string, paramName: string, isOptional) => {
params.push({ paramName, isOptional: isOptional != null });
return isOptional ? "/?([^\\/]+)?" : "/([^\\/]+)";
return isOptional ? "(?:/([^\\/]*))?" : "/([^\\/]+)";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Making the question mark optional can cause issues when there is a suffix after the param - here's a failing case:

expect(matchPath("/sitemap/:lang?.xml", "/sitemap.xml")?.params).toBeNull();

Normally we do require the params to be the full segment, but the dot notation slipped through out original implementations and enough apps rely on it that it would be a breaking change at this point so we plan to continue supporting it.

Can you see if there's a different solution that plays nicely with that format?

@nwleedev
Copy link
Copy Markdown
Contributor Author

Checked the suffix case you mentioned and adjusted the approach to preserve dot-notation compatibility.

  • Added a regression test to ensure matchPath("/sitemap/:lang?.xml", "/sitemap.xml") returns null.
  • Kept existing dot-notation behavior working.
    • "/sitemap/en.xml" matches with { lang: "en" }
    • "/sitemap/.xml" matches with { lang: undefined }
  • Updated compilePath so optional params without a suffix require the slash+param to appear together.
    • Prevented cases like /test_route_more matching /test_route/:part?.
    • For optional params with a same-segment suffix, the segment is not made fully optional.

@brophdawg11 brophdawg11 self-requested a review January 16, 2026 14:36
@brophdawg11
Copy link
Copy Markdown
Contributor

Thanks!

@brophdawg11 brophdawg11 merged commit 7535805 into remix-run:dev Jan 21, 2026
5 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🤖 Hello there,

We just published version 7.13.1-pre.0 which includes this pull request. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

@github-actions
Copy link
Copy Markdown
Contributor

🤖 Hello there,

We just published version 7.13.1 which includes this pull request. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants