Skip to content

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

@SisyphusZheng

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions