Skip to content

Programmatic routes via app.route() bypass specificity sorting #3861

Description

@SisyphusZheng

Problem

Routes registered via app.route() / app.get() are matched in registration order, not by specificity. This means if a user registers a catch-all before a specific route, the specific route is silently shadowed:

app.route("/blog/[...rest]", handlerA);  // registered first → matched first
app.route("/blog/[id]", handlerB);        // NEVER reached

File-system routes are protected from this by sortRoutePaths() in fs_crawl.ts, which sorts before insertion. But programmatic routes bypass sorting entirely — commands go into this.#commands in registration order → applyCommandsInner processes them in that order → router.add() inserts them in that order → UrlPatternRouter matches first-wins.

Steps to reproduce

const app = new App();
app.route("/api/[...rest]", () => new Response("catch-all"));
app.route("/api/health", () => new Response("health"));
// GET /api/health → returns "catch-all" (WRONG)

Proposed fix

Apply the same sortRoutePaths sorting to programmatic routes. In applyCommandsInner, sort the Route and Handler command groups before processing, or sort #dynamicArr in UrlPatternRouter.add() when a new pattern is inserted.

The sorting criteria should match sortRoutePaths semantics: static segments > dynamic params [id] > catch-all [...rest].

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions