[]previous and next page

Precedence, private routes and matchFilters on API routes

A request path can match a page, an API route and a stream route at once (/f/:id/preview matches /f/:id/:name).

For agentsUpdated

A request path can match a page, an API route and a stream route at once (/f/:id/preview matches /f/:id/:name). The most specific pattern answers, whatever its kind: at the first segment where two patterns differ, a static segment beats a param, a param beats an optional param, and an optional param beats a splat. So the page /f/:id/preview answers before the API route /f/:id/:name, and an API route /f/:id/special answers before a page /f/:id/:name. A more specific route answers even a method it does not handle (its 405 is the answer); the less specific one is not tried.

Two routes of one shape (the same path) are told apart by the method. An API route answers the methods it exports (HEAD through GET, and OPTIONS always); a page answers GET, HEAD and OPTIONS. A page and an API route may share a path when they claim different methods (a page for GET, an API route for POST). The scanner refuses the pair only when both claim the same method (ROUTE_PAGE_API_CONFLICT, naming both files).

Private routes. A _-prefixed folder or file (_preview/[fileId].tsx, api/_internal/ping.ts) is private by convention. The scanner marks it, and a request from outside never matches it: it answers 404 like any unknown path. A rewrite target may be private: a middleware rewrite("/_preview/9") (or a rewrites rule of dusk.config.ts) answers with that page, and the address the client asked for stays in the browser. A private page cannot be prerendered (the static file would publish it): the scanner refuses a prerender export there (ROUTE_PRIVATE_PRERENDER). The matcher's side is isPrivatePattern and RouteVisibility ("public" or "all") in @shadow-js/router/core.

matchFilters on API routes. An API module exports matchFilters as a page does (export const matchFilters = { id: /^\d+$/ }). A value that fails its rule is not a match: the request is a 404. The module is imported at startup for its rules; its handlers still load lazily.

Allow lists and unknown methods (G-B8)#

When a page and an API route both match a path, a refused method is answered with the union of their methods, not with one route's list. A page answers GET, HEAD and OPTIONS; an API route answers the methods it exports, plus HEAD (from GET) and OPTIONS. So PUT to a path whose page is GET-only and whose API route exports POST is a 405 with Allow: GET, HEAD, POST, OPTIONS, and OPTIONS on that path is a 204 with the same list. A known method that no route on the path answers is a 405. A method token Dusk does not know at all (TRACE, PURGE, FOO) is a 501 Not Implemented with Allow listing the seven methods Dusk handles. No route can change that answer, and ALL does not answer such a token.