Skip to content

Commit a36b75d

Browse files
feat: add defineRoute and defineRoutes declarative helpers
Removes the handler-closure boilerplate consumers repeat per route: page, loading, and error components receive the route context (params, query) as props, and loader/meta/extra receive it as their first argument. defineRoutes groups routes and supports per-key page component overrides while preserving loaders, meta, extra, options, and route metadata. Adds react as a peer dependency (the helpers use createElement to bind context into components). Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ffd7552 commit a36b75d

6 files changed

Lines changed: 601 additions & 3 deletions

File tree

README.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ A simple, type-safe router for React that works with any framework.
44

55
## Why use this?
66

7-
-**Super Simple** - Only 2 functions: `createRoute` and `createRouter`
7+
-**Super Simple** - A tiny API: `createRoute` and `createRouter` (plus optional `defineRoute`/`defineRoutes` sugar)
88
- 🔒 **Type Safe** - TypeScript knows your route params automatically
99
- 🎯 **Flexible** - Works with any React framework
1010
-**Validated** - Built-in query parameter validation
@@ -103,6 +103,44 @@ const staticRoutes = Object.values(router.routes)
103103
.filter(route => route.meta?.isStatic);
104104
```
105105

106+
## Declarative Routes (less boilerplate)
107+
108+
If you don't need custom closures per route, `defineRoute` and `defineRoutes` remove the handler wiring entirely. Components receive the route context (`params`, `query`) as props, and `loader`/`meta`/`extra` receive it as their first argument:
109+
110+
```tsx
111+
import { defineRoute, defineRoutes, createRouter } from "@btst/yar";
112+
113+
// Single route
114+
const postRoute = defineRoute("/blog/:slug", {
115+
page: BlogPostPage, // rendered with { params, query } as props
116+
loading: Spinner,
117+
error: ErrorPage,
118+
loader: (ctx, signal?: AbortSignal) =>
119+
fetch(`/api/posts/${ctx.params.slug}`, { signal }).then((r) => r.json()),
120+
meta: (ctx, post) => [{ name: "title", content: post?.title ?? ctx.params.slug }],
121+
extra: (ctx) => ({ breadcrumbs: ["Home", "Blog", ctx.params.slug] }),
122+
});
123+
124+
// Many routes at once, with optional page overrides
125+
const routes = defineRoutes(
126+
{
127+
home: defineRoute("/", { page: HomePage }, undefined, { isStatic: true }),
128+
post: defineRoute("/blog/:slug", {
129+
page: BlogPostPage,
130+
loader: (ctx) => fetchPost(ctx.params.slug),
131+
meta: (ctx) => [{ name: "title", content: ctx.params.slug }],
132+
}),
133+
},
134+
{ pages: { post: CustomPostPage } } // swap a page component per key
135+
);
136+
137+
const router = createRouter(routes);
138+
```
139+
140+
Overridden pages still receive the route context (`params`, `query`) as props. `defineRoutes` also accepts plain `createRoute` routes; overriding those swaps the `PageComponent` as-is.
141+
142+
`defineRoute` returns a regular route, so it composes freely with `createRoute` routes in the same `createRouter` call. Use `createRoute` when you need full control over the handler closure; use `defineRoute`/`defineRoutes` for the common declarative case.
143+
106144
## What You Need to Know
107145

108146
### `createRoute(path, handler, options?, routeMeta?)`

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@btst/yar",
3-
"version": "1.2.0",
3+
"version": "1.3.0",
44
"packageManager": "pnpm@10.14.0",
55
"description": "Pluggable router for modern react frameworks",
66
"type": "module",
@@ -44,13 +44,15 @@
4444
"@biomejs/biome": "2.2.4",
4545
"@types/react": "^19.1.16",
4646
"@types/react-dom": "^19.1.9",
47+
"react": "^19.1.1",
4748
"tsup": "^8.5.0",
4849
"typescript": "^5.9.2",
4950
"vitest": "^3.2.4"
5051
},
5152
"peerDependencies": {
5253
"@types/react": "^19.1.16",
53-
"@types/react-dom": "^19.1.9"
54+
"@types/react-dom": "^19.1.9",
55+
"react": "^18.0.0 || ^19.0.0"
5456
},
5557
"exports": {
5658
".": {

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)