fix(dart_frog_gen): order route mounts by specificity#1969
Open
Yusufihsangorgel wants to merge 1 commit into
Open
fix(dart_frog_gen): order route mounts by specificity#1969Yusufihsangorgel wants to merge 1 commit into
Yusufihsangorgel wants to merge 1 commit into
Conversation
Yusufihsangorgel
requested review from
alestiago,
erickzanardo,
felangel,
marcossevilla,
renancaraujo and
wolfenrain
as code owners
July 8, 2026 21:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
READY
Description
dart_frog build/dart_frog devcan generate a root router where a shortprefix mount is registered before the longer literal paths nested under it.
With the layout from the issue:
codegen emitted:
Router.mount('/…/tasks', …)also matches/…/tasks/<rest>and falls throughon a 404, so
POST /…/tasks/addwas captured by thetasksrouter and servedby its
/<taskId>file withtaskId == "add"instead of hittingadd/.Root cause
The order of the flat root-level mounts is the order of
RouteConfiguration.directories. Since #1931 (which removed the.reversedin the pre-gen hooks to fix #1930), that list is the rawdepth-first order — parent directory before its children — i.e. the generic
prefix is mounted first.
#1930 and #1959 are the two mirror images of the same ordering problem:
/booksmount (popular.dart)/books/<id>mount/booksbefore/books/<id>/tasks/addmount/tasksmount ([taskId].dart)/tasks/addbefore/tasksNeither "generic first" nor "longest first" satisfies both. The fix orders the
mounts by segment specificity, most specific first, where per position a
static segment out-ranks the end of a shorter route, which in turn out-ranks a
dynamic segment:
static (2) > end-of-path (1) > dynamic (0)That yields
/tasks/addbefore/tasks(#1959) while keeping/booksbefore
/books/<id>(#1930), because/booksends where/books/<id>has a dynamic segment.
Fix
buildRouteConfigurationnow sortsdirectorieswith a newcompareRouteDirectorySpecificitycomparator (ties fall back to the routestring for a stable, deterministic order). This is done in
dart_frog_gen,the single source both the dev-server and prod-server hooks consume, keeping
the ordering decision in one place — consistent with #1931 which made the
hooks trust the order coming out of
buildRouteConfiguration.Generated output after the change:
Verified end to end (local CLI + real generated prod build):
Note on #1958
The same ordering change also helps
#1958
(
_middleware.dartexecuted multiple times per request), but does not fullyclose it, so this PR does not claim it.
For the minimal layout in #1958 (
routes/v2/_middleware.dart,routes/v2/index.dart,routes/v2/person/[personId]/address.dart) the parentmiddleware went from 2 executions → 1 for a single
GET /v2/person/123/address: with the specificity order,/v2/person/<personId>is mounted before/v2, so it matches first andreturns 200 without the request ever falling through the
/v2prefix mount.However, the root cause of #1958 is architectural. dart_frog flattens nested
routers into root-level
mount()s, and everyRouter.mount('/prefix', …)registers a
/prefix/<rest>fallthrough matcher. Whenever a request has topass an intermediate prefix mount that 404s — e.g. add
routes/v2/person/index.dartso a
/v2/personmount exists — that prefix still matches, runs the cascadedparent middleware, 404s, and falls through, so the middleware runs twice again.
Ordering cannot avoid that; a full fix needs a change to how nested routers are
mounted, which is out of scope here.
Type of Change