One of the worst things about Next.js, Remix etc. is their file system driven routes. I really wish these frameworks would stop relying so much on hidden magic. Conventions are good, but as to why those conventions aren't <i>in code</i> is quite peculiar.<p>Previously, I wrote my route definitions with types for both path params and query params in one file, and used TypeScript to enforce that the equivalent back-end definitions (async loaders etc.) and front-end definitions (React components) were kept in sync.<p>When I first implemented this in a previous project, I found <i>many</i> instances where routes were expecting query params but they were being dropped off (e.g. post login redirects).<p>Supporting things like parameters for nested routes certainly means the TS types themselves are non-trivial, but they're the kind of thing you write (and document) once, but benefit from daily.<p>Examples of stuff that can and <i>should</i> be 100% type checked:<p><pre><code> // ...
template: {
path: idPath("/template"),
subroutes: {
edit: { path: () => "/edit" },
remix: { path: () => "/remix" },
},
},
onboarding: {
path: () => "/onboarding",
query: (params: { referralCode?: string }) => params,
subroutes: {
createYourAvatar: { path: () => "/createyouravatar" },
},
},
// ...
</code></pre>
Routing:<p><pre><code> // Path params
navigate(routes.template.edit({ id: props.masterEdit.masterEditId }));
// No path params, just query params (null path params)
navigate(routes.onboarding(null, { referralCode }))
// Nested route with query params (inherited from parent route)
navigate(routes.onboarding.createYourAvatar(null, { referralCode }))
</code></pre>
React hooks:<p><pre><code> // Path params
const { id } = useRouteParams(routes.template.edit)
// Query params
const { referralCode } = useRouteQueryParams(routes.onboarding);
</code></pre>
API routes:<p><pre><code> // ...
play: {
path: () => "/play",
subroutes: {
episode: {
path: idPath("/episode"),
},
},
},
// ...
</code></pre>
Relative route paths (for defining nested express routers):<p><pre><code> const routes = relativeApiRoutes.api.account;
router.post(routes.episode(), async (req: express.Request, res: express.Response) => {</code></pre>