Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue with route ordering for ranking ties #7884

Closed
wants to merge 8 commits into from

Conversation

brophdawg11
Copy link
Contributor

@brophdawg11 brophdawg11 commented Nov 2, 2023

Fix route ordering discrepancies between build.routes and build.assets.routes

It's possible for the current React Router ranking algorithm to end up with ties, and in those cases the first matching route "wins". In React Router you can manage these tie breakers by the order in which you define your routes. But in Remix they are (by default) defined by the order of the route files on the file system.

The bug being fixed here was that in our flat routes logic we did some sorting by length to determine parent routes, and this would result in the eventual defined routes being sorted in the same manner (build.routes). However, our actual manifest generation happens in the order of the esbuild metafile outputs (build.assets.routes).

If these two ended up in different orders we could end up with out of sync route matching in the server-runtime and <RemixServer>. The specific collision encountered is represented in the integration tests in this PR.

This PR updates the routes and manifest to always respect the original order of the incoming config.routes, which accomplished 2 things:

  1. The routes and manifest are always synced
  2. Users can take manual control over tiebreakers by ignoring file routes and adding them in their preferred order in remix.config.js since the config order is the source of truth.

Repro: https://stackblitz.com/edit/remix-run-remix-5udtin

  • Broken on 2.11.2 and confirmed fixed by 0.0.0-experimental-432fae462 built from this branch

Copy link

changeset-bot bot commented Nov 2, 2023

🦋 Changeset detected

Latest commit: 432fae4

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

This PR includes changesets to release 16 packages
Name Type
@remix-run/dev Patch
create-remix Patch
remix Patch
@remix-run/architect Patch
@remix-run/cloudflare Patch
@remix-run/cloudflare-pages Patch
@remix-run/cloudflare-workers Patch
@remix-run/css-bundle Patch
@remix-run/deno Patch
@remix-run/eslint-config Patch
@remix-run/express Patch
@remix-run/node Patch
@remix-run/react Patch
@remix-run/serve Patch
@remix-run/server-runtime Patch
@remix-run/testing 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

@brophdawg11 brophdawg11 marked this pull request as ready for review November 3, 2023 17:10
await app.goto("/legal/apple-pay");
await page.waitForSelector("h1");
expect(await app.getHtml("h1")).toMatch(":locale?/apple-pay");
expect(await app.getHtml("pre")).toMatch(":locale?/apple-pay");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The tie here occurs because ($locale).apple-pay.jsx and ($locale).legal.$article.jsx explode out to the following in the react router routes:

:locale/apple-pay
/apple-pay
:locale/legal/:article
legal/:article

And then the URL /legal/apple-pay matches both of these routes with an identical score:

:locale/apple-pay
legal/:article

In this first test, we expect ($locale).apple-pay.jsx to win due to alphabetical ordering

await app.goto("/legal/apple-pay");
await page.waitForSelector("h1");
expect(await app.getHtml("h1")).toMatch(":locale?/legal/:article");
expect(await app.getHtml("pre")).toMatch(":locale?/legal/:article");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this test, we expect ($locale).legal.$article.jsx to win because we included it first in remix.config.js routes

@@ -163,6 +164,7 @@ export function flatRoutesUniversal(
}

routeIds.set(routeId, normalizedFile);
configRouteOrder.set(routeId, idx);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Capture the order of routeIds in the config so we can sort by them later

@brophdawg11 brophdawg11 changed the title POC to fix issue with route ordering for ranking ties Fix issue with route ordering for ranking ties Nov 6, 2023
@brophdawg11
Copy link
Contributor Author

Huh - this used to only fail the Windows E2E tests and we didn't know why nor did we have a chance to dig too deep into what was going on there, but they pass now so I'm not 100% sure what fixed it - nor are the logs available for those CI runs anymore. I'm assuming we fixed some sort of other bug in our windows dev setup along the way...

let sortedRoutes = Object.keys(routes)
.sort((a, b) => (order[a] < order[b] ? -1 : order[a] > order[b] ? 1 : 0))
.reduce((acc, id) => Object.assign(acc, { [id]: routes[id] }), {});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Vite does not have this issue because it generates the manifest in the order it gets from remixConfig.routes so the order is already correct there after the fix to flat-routes.ts below.

@brophdawg11
Copy link
Contributor Author

From what I can tell this was only an issue in the esbuild compiler where the flat routes and manifest could end up in different orders causing hydration issues. With Vite - the manifest is generated from the output of the flat routes + config routes logic so the inconsistency is eliminated.

I think technically it still makes sense to re-sort the flat routes logic alphabetically as we do in this PR but I think that could be interpreted as a breaking change in v2. RR v7 will be moving towards routes.ts anyway which will give the user's full control so I'm not inclined to merge this fix to v2 for esbuild-only apps.

@brophdawg11 brophdawg11 deleted the brophdawg11/route-sorting branch August 20, 2024 20:26
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