Skip to content

[go_router_builder] Report duplicate route paths at build time #190741

Description

@brianegan

Use case

As a developer

  • Given I declare two separate routes with the same path
  • When, I run go_router_builder
  • Then, I want to know about my mistake at build time so I can fix it. Otherwise, I run into unexpected behavior at runtime.

Current behavior

First declared wins, but not always. I tested all three cases against go_router 17.3.0.

Duplicate leaf paths: the first wins, the second is dead. Configuration._getLocRouteMatches walks the root routes in declaration order and returns the first non-empty result (configuration.dart:395-405). Children do the same and break on the first hit (match.dart:264-277). Two details leaves under /home, and /home/details renders the first.

Paths differing only in parameter name: same, first wins. /meal/:id before /meal/:mealId, navigating to /meal/7 renders the first and populates id with 7. The second builder never runs, so mealId is never read. A failed attempt doesn't leak parameters either, since each route stages them in a temp map and merges only once it's part of the result (match.dart:216-225).

Duplicate paths with different children: the second is reachable. This is the one that breaks the simple story. Matching backtracks. A route that matches a prefix but whose children can't complete the URL returns empty (match.dart:278-282), and the sibling loop keeps going. So with:

/home
  details -> [a]
  details -> [b]

/home/details/b reaches child b through the second details. I confirmed it renders. /home/details on its own still resolves to the first.

Proposal

  • Detect the conflict in go_router_builder and report it at build time. Two routes conflict when they compete for the same URL namespace and their paths normalize to the same pattern, so meal/:id and meal/:mealId count as one pattern.
  • Resolve namespaces per level of the route tree. Shell routes and StatefulShellRoute branches own no path, so the routes inside them compete with the routes around them. Relative routes own their path like any other route. Separate annotations compete, since $appRoutes collects them into one list. A library includes its part files, which covers an app's whole route table given it has to live in one library today ([go_router_builder] Define routes in separate files #122258).
  • Report rather than fail, by default. The third case above works at runtime, because matching backtracks, so a hard error would fail the build of an app that runs correctly. Same for one route class declared twice with different children, which is a way to group children by feature area. Every route still generates and the author decides what to do.
  • Make the severity configurable with a duplicate_route_paths builder option, accepting warning (the default), error, and ignore. Teams whose duplicates are always mistakes get a hard failure, and teams using the grouping pattern set ignore. Package-wide, with no per-route escape hatch.
  • Worth noting the check has already earned its keep: go_router_builder's own example app registered DoubleRoute twice since [go_router_builder] Adds support for enhanced enums. packages#2395 (2022) and DoubleExtensionRoute twice since [go_router_builder] Support extension types packages#9458 (2025). Both duplicates were unreachable, and removing them changed nothing but the dead entries in the generated route list.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projecta: error messageError messages from the Flutter frameworkc: proposalA detailed proposal for a change to Flutterp: go_router_builderThe go_router_builder packagepackageflutter/packages repository. See also p: labels.team-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework teamwaiting for PR to land (fixed)A fix is in flight

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions