The Generated Route Registry
paramour generate turns your filesystem routes into a compile-time union.
paramour generate scans your app directory and emits a types-only module
augmentation, so defining a route whose path doesn't exist on disk becomes a
compile error — with zero runtime cost.
The problem it solves
A route object declares its path as a string:
defineAppRoute("/product/[id]", …). The compiler can check everything
about that string — params matching segments, codecs matching types — but
it cannot know whether app/product/[id]/page.tsx actually exists. Filesystem
truth lives outside the type system.
The registry imports it. paramour generate scans app/ (and pages/, if
present) and writes paramour-env.d.ts:
// Generated by @paramour-js/next. Do not edit — regenerate with `paramour generate`.
import "paramour";
declare module "paramour" {
interface ParamourRegister {
: "/" | "/product/[id]";
}
}That's the whole artifact: a types-only module augmentation teaching the compiler which paths exist, per router. No generated runtime code, no build step in your bundle — it compiles to nothing.
What it buys
Once the augmentation is in your program, the route constructors' path argument narrows from "any plausible string" to the registered union. Typos, renamed directories, and router mix-ups all become squiggles:
// @filename: paramour-env.d.ts
import "paramour";
declare module "paramour" {
interface ParamourRegister {
: "/" | "/product/[id]";
: "/legacy/[id]";
}
}
// @filename: routes.ts
import { , , } from "paramour";
("/totally/made/up", {});
("/product/[productId]", { : { : .() },
});
("/product/[id]", { : { : .() } });The middle one is the case that pays rent: /product/[id] exists, but the
definition says [productId] — exactly the near-miss that survives code
review and fails at 2 a.m. The last shows the router gate: /product/[id]
is an App route, so the Pages constructor rejects it.
Before the first generate, no augmentation exists and the constructors accept any well-formed path — the library degrades gracefully rather than blocking you. The registry is what upgrades "plausible" to "real."
The lifecycle
The artifact is generated and committed — treat it like a lockfile:
- Edit routes — add, move, or delete a page directory.
paramour generate— rewritesparamour-env.d.ts(the"paramour"package script that init added runs exactly this). Duringnext dev, thewithTypedRouteswrapper keeps it current automatically as routes appear and disappear.paramour checkin CI — re-scans and compares. On drift it exits non-zero and never writes: a route deleted on disk but still registered (or vice versa) fails the build instead of lying to the compiler.
Committing the artifact means the registry works for everyone who checks out the repo — code review shows route changes as visible diffs to a small, readable file, and CI proves that diff honest. (Don't hand-edit or format it; it's regenerated verbatim.)
This site practices what this page preaches: every docs page you're reading
is served by paramour routes, and the docs build runs paramour check
before next build — registry drift here fails our CI too.
Where next
- CLI workflows —
generate,check, and the rest of the toolchain in depth, including watch mode and CI recipes. - Registry types reference —
ParamourRegisterand theRegistered*Pathslookups the artifact populates. - Getting Started — the registry in the context of a full setup, if you landed here first.