@chidchanun/bcp 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,47 @@
2
2
 
3
3
  All notable framework changes are tracked here before release.
4
4
 
5
+ ## 0.1.10 - Framework updater and protected route guards
6
+
7
+ ### Framework updater
8
+
9
+ - Added `bcp update` to resolve and install the current `@chidchanun/bcp@latest` release from an existing BCP application.
10
+ - Added `bcp update <version-or-tag>` for explicitly targeting a published version or npm dist-tag.
11
+ - Added `bcp update --check` and `bcp update --dry-run` so projects can inspect an available update without changing package metadata or lockfiles.
12
+ - Existing generated `bcp` npm aliases and direct `@chidchanun/bcp` dependencies are detected automatically.
13
+ - Package-manager detection supports npm, pnpm, Yarn and Bun lockfiles and refuses to guess when conflicting lockfiles are present.
14
+ - Failed installs restore the original `package.json` and detected lockfile before returning an error.
15
+ - Projects published before the updater can bootstrap it once with `npx @chidchanun/bcp@latest update`; subsequent releases can use `bcp update` or the generated `npm run update` script directly.
16
+ - Generated applications pin the exact resolved BCP version so a normal package-manager install does not silently cross framework releases.
17
+ - Added unit coverage for updater argument parsing, dependency planning, package-manager detection and ambiguous dependency rejection.
18
+ - Added publish-artifact smoke checks requiring the updater source and CLI dispatch wiring.
19
+
20
+ ### Protected routes and auth guards
21
+
22
+ - Added scoped `guard.ts` / `guard.tsx` files that protect pages in their route directory and descendant page directories.
23
+ - Guards execute from app root toward the matched page and receive `params`, target `searchParams` and merged ancestor `parentData`.
24
+ - A guard may return JSON-safe authorization data or a Web `Response`; redirects stop the pipeline before loader/page execution.
25
+ - Guards run inside the normal BCP request context, so sessions, cookies, headers, request URLs, request IDs and other `bcp/server` helpers are available directly.
26
+ - Loader context now receives merged `guardData`, avoiding repeated session/role lookups after a guard has already validated the request.
27
+ - Added public `useGuardData<T>()` so pages can consume the same guard result during SSR, hydration and SPA navigation.
28
+ - Guard-only routes are supported without requiring a page loader.
29
+ - Response cookies set by allowed guards or redirecting guards are preserved on document and navigation responses.
30
+ - Guard return values use the same JSON-safe validation rules as loader data and reject unsupported/circular values before transport.
31
+ - Direct requests and SPA navigation use the same authorization policy, including target-page params/query values and redirects.
32
+ - Standalone production generates a server-only `guards.mjs` evaluator and places the guard gateway after project middleware but before loader/page execution.
33
+ - User-supplied internal guard transport headers are stripped before authorization evaluation so clients cannot forge guard results.
34
+ - Internal serialized guard data is size-limited before it is proxied to the inner standalone runtime.
35
+ - Route-guarded pages are excluded from the automatic production response cache because their output may depend on session/role identity.
36
+ - Added `/guard-demo/[id]` and guard-only fixtures covering parent/child guard data, roles, cookies, loader integration and redirects.
37
+ - Added unit, development integration and standalone E2E coverage for guards, including forged-header rejection and guard-only production routes.
38
+ - Added publish-artifact smoke checks for the guard evaluator, standalone guard runtime, public hook and loader/guard bridge.
39
+ - Added dedicated protected-route documentation and updated README guidance for middleware versus route guards.
40
+
41
+ ### Release channel
42
+
43
+ - Stable BCP releases now publish to the npm `latest` dist-tag by default even while the framework is pre-1.0.
44
+ - Maintainers can still select `next`, `beta` or another channel explicitly through `BCP_DIST_TAG`.
45
+
5
46
  ## 0.1.9 - Server data loaders
6
47
 
7
48
  ### Server rendering and data
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  BCP Framework is a React full-stack framework with file-based routing, SSR, client navigation, API routes, middleware, metadata, client islands, cache/revalidation, security defaults and standalone production builds.
4
4
 
5
- > Current development version: `0.1.9`. BCP is still pre-1.0 and validates each release candidate before the manual npm publish step.
5
+ > Current release target: `0.1.10`. BCP is still pre-1.0 and validates each release candidate before the manual npm publish step.
6
6
 
7
7
  ## Quick start
8
8
 
@@ -33,10 +33,13 @@ app/
33
33
  ├─ loading.tsx
34
34
  ├─ error.tsx
35
35
  ├─ not-found.tsx
36
- ├─ users/
37
- └─ [id]/
38
- ├─ loader.ts
39
- └─ page.tsx
36
+ ├─ dashboard/
37
+ ├─ guard.ts
38
+ ├─ page.tsx
39
+ └─ users/
40
+ │ └─ [id]/
41
+ │ ├─ loader.ts
42
+ │ └─ page.tsx
40
43
  └─ api/
41
44
  └─ hello/
42
45
  └─ route.ts
@@ -74,7 +77,7 @@ Server-only modules can declare:
74
77
  import "bcp/server-only";
75
78
  ```
76
79
 
77
- Database helpers generated by `create-bcp-app` include the server-only marker automatically. A server-only helper must not be imported from a hydrated page/client graph. Use an API route or, for page data in BCP 0.1.9+, a route-level `loader.ts` that runs only on the server.
80
+ Database helpers generated by `create-bcp-app` include the server-only marker automatically. A server-only helper must not be imported from a hydrated page/client graph. Use an API route or a server-only route primitive such as `loader.ts` or `guard.ts`.
78
81
 
79
82
  See [Application Modules](docs/application-modules.md) for the complete boundary model and examples.
80
83
 
@@ -89,11 +92,42 @@ bcp dev
89
92
  bcp routes
90
93
  bcp build
91
94
  bcp start
95
+ bcp update
92
96
  bcp version
93
97
  ```
94
98
 
95
99
  CLI server overrides are available with `--port` and `--hostname`.
96
100
 
101
+ ## Updating an existing project
102
+
103
+ Once a project is on a BCP version that contains the updater, update to the current npm `latest` release with:
104
+
105
+ ```bash
106
+ bcp update
107
+ ```
108
+
109
+ Preview an update without changing files:
110
+
111
+ ```bash
112
+ bcp update --check
113
+ bcp update --dry-run
114
+ ```
115
+
116
+ Or select a published version/dist-tag explicitly:
117
+
118
+ ```bash
119
+ bcp update 0.1.10
120
+ bcp update next
121
+ ```
122
+
123
+ Versions published before the updater do not recognize `bcp update`. Bootstrap the newest CLI once from those projects:
124
+
125
+ ```bash
126
+ npx @chidchanun/bcp@latest update
127
+ ```
128
+
129
+ The updater changes the framework dependency and package-manager lockfile, detects npm/pnpm/Yarn/Bun from the project lockfile, and restores package metadata if installation fails. It does not overwrite application source files, database schemas or authentication code. See [Updating BCP Framework](docs/updating.md) for the complete behavior and migration notes.
130
+
97
131
  ## Routing
98
132
 
99
133
  BCP supports:
@@ -124,7 +158,7 @@ Supported methods include GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS. HEAD
124
158
 
125
159
  ## Server request APIs
126
160
 
127
- BCP 0.1.7 adds request-scoped server helpers through `bcp/server`. These helpers are server-only and are available from API handlers and server data loaders.
161
+ BCP 0.1.7 adds request-scoped server helpers through `bcp/server`. These helpers are server-only and are available from API handlers, server data loaders and route guards.
128
162
 
129
163
  ```ts
130
164
  import {
@@ -273,6 +307,98 @@ Loader-backed pages now use the same SPA navigation model as normal BCP routes.
273
307
 
274
308
  See [Server Data Loaders](docs/server-data-loaders.md) for serialization rules, authentication patterns, redirect behavior, SPA navigation, cache safety and request-context semantics.
275
309
 
310
+ ## Protected routes and auth guards
311
+
312
+ The `0.1.10` release target adds scoped `guard.ts` / `guard.tsx` files for page authorization. A guard protects the pages in its directory and descendant route directories, runs before the page loader, and can use the same request/session APIs as a loader.
313
+
314
+ ```text
315
+ app/dashboard/
316
+ ├─ guard.ts
317
+ ├─ page.tsx
318
+ └─ users/
319
+ └─ [id]/
320
+ ├─ loader.ts
321
+ └─ page.tsx
322
+ ```
323
+
324
+ ```ts
325
+ // app/dashboard/guard.ts
326
+ import {
327
+ getSession,
328
+ redirect,
329
+ } from "bcp/server";
330
+
331
+ export async function guard() {
332
+ const session =
333
+ await getSession<{
334
+ userId: number;
335
+ email: string;
336
+ role: string;
337
+ }>();
338
+
339
+ if (!session) {
340
+ return redirect(
341
+ "/login",
342
+ 303
343
+ );
344
+ }
345
+
346
+ return {
347
+ session,
348
+ role:
349
+ session.role,
350
+ };
351
+ }
352
+ ```
353
+
354
+ Descendant loaders receive the merged result as `guardData`, so they do not need to repeat `getSession()`:
355
+
356
+ ```ts
357
+ export async function loader({
358
+ params,
359
+ guardData,
360
+ }) {
361
+ return {
362
+ id:
363
+ params.id,
364
+ user:
365
+ guardData.session,
366
+ role:
367
+ guardData.role,
368
+ };
369
+ }
370
+ ```
371
+
372
+ A page can consume the same authorization data with `useGuardData<T>()`:
373
+
374
+ ```tsx
375
+ "use client";
376
+
377
+ import {
378
+ useGuardData,
379
+ } from "bcp";
380
+
381
+ export default function DashboardPage() {
382
+ const guard =
383
+ useGuardData<{
384
+ session: {
385
+ email: string;
386
+ };
387
+ role: string;
388
+ }>();
389
+
390
+ return (
391
+ <main>
392
+ {guard.session.email}
393
+ </main>
394
+ );
395
+ }
396
+ ```
397
+
398
+ Nested guards execute from root to child and receive ancestor output as `parentData`. Guard redirects and response cookies work for both direct document requests and SPA navigation. Guarded pages are excluded from the automatic production response cache, and standalone production strips user-supplied internal guard transport headers before evaluating authorization.
399
+
400
+ See [Protected Route Guards](docs/route-guards.md) for nested role policies, serialization rules, middleware ordering, cookie behavior and production hardening.
401
+
276
402
  ## JWT cookie sessions
277
403
 
278
404
  BCP 0.1.8 adds HS256 JWT cookie sessions directly to `bcp/server`. Configure a server-only secret of at least 32 bytes:
@@ -412,7 +538,7 @@ import {
412
538
  } from "bcp/cache";
413
539
  ```
414
540
 
415
- The current cache implementation is process-local and intentionally does not provide distributed invalidation across multiple Node.js instances. Loader-backed pages are excluded from the automatic production response-cache manifest in 0.1.9; cache loader data explicitly only when the application has a safe user-aware cache key and invalidation strategy.
541
+ The current cache implementation is process-local and intentionally does not provide distributed invalidation across multiple Node.js instances. Loader-backed and route-guarded pages are excluded from the automatic production response-cache manifest because their output may depend on request/session identity. Cache server data explicitly only when the application has a safe user-aware cache key and invalidation strategy.
416
542
 
417
543
  ## Middleware
418
544
 
@@ -437,6 +563,8 @@ export function middleware(
437
563
  }
438
564
  ```
439
565
 
566
+ Use middleware for request-wide interception and `guard.ts` for page-subtree authorization that needs to feed identity/role data into loaders and pages. In standalone production, project middleware runs before route guards.
567
+
440
568
  ## Configuration
441
569
 
442
570
  ```ts
@@ -488,10 +616,13 @@ The standalone output is generated under:
488
616
  └─ server/
489
617
  ├─ server.mjs
490
618
  ├─ middleware.mjs
619
+ ├─ guards.mjs
491
620
  ├─ cache-manifest.json
492
621
  └─ config.json
493
622
  ```
494
623
 
624
+ `guards.mjs` is generated only when the application contains protected route guards.
625
+
495
626
  ## Package preparation
496
627
 
497
628
  The development monorepo stays private. Publishable artifacts are produced separately:
@@ -523,7 +654,9 @@ No real npm publish command is run automatically by the repository.
523
654
  - [Application Modules](docs/application-modules.md)
524
655
  - [Server Request APIs](docs/server-request-apis.md)
525
656
  - [Server Data Loaders](docs/server-data-loaders.md)
657
+ - [Protected Route Guards](docs/route-guards.md)
526
658
  - [JWT Cookie Sessions](docs/session-auth.md)
659
+ - [Updating BCP Framework](docs/updating.md)
527
660
  - [Routing](docs/routing.md)
528
661
  - [Configuration](docs/configuration.md)
529
662
  - [Caching](docs/caching.md)
package/docs/releasing.md CHANGED
@@ -18,7 +18,7 @@ Application source continues importing from `bcp`. `create-bcp-app` stores the s
18
18
  Use the version helper instead of editing package metadata manually:
19
19
 
20
20
  ```bash
21
- npm run version:set -- 0.1.1
21
+ npm run version:set -- 0.1.10
22
22
  ```
23
23
 
24
24
  It synchronizes the release version across:
@@ -187,24 +187,29 @@ and the executable:
187
187
  bcp
188
188
  ```
189
189
 
190
- A generated application's `package.json` should therefore contain a dependency equivalent to:
190
+ A newly generated application's `package.json` pins the selected framework release exactly so a later plain package-manager install cannot silently move BCP to a different release:
191
191
 
192
192
  ```json
193
193
  {
194
+ "scripts": {
195
+ "update": "bcp update"
196
+ },
194
197
  "dependencies": {
195
- "bcp": "npm:@chidchanun/bcp@^0.1.1"
198
+ "bcp": "npm:@chidchanun/bcp@0.1.10"
196
199
  }
197
200
  }
198
201
  ```
199
202
 
203
+ The explicit updater is responsible for resolving and installing later framework releases.
204
+
200
205
  ## 8. Create the release tag
201
206
 
202
207
  Only after `npm run rc:check` passes and `CHANGELOG.md` is ready:
203
208
 
204
209
  ```bash
205
210
  git status
206
- git tag -a v0.1.1 -m "BCP Framework v0.1.1"
207
- git push origin v0.1.1
211
+ git tag -a v0.1.10 -m "BCP Framework v0.1.10"
212
+ git push origin v0.1.10
208
213
  ```
209
214
 
210
215
  Use the actual version from `package.json` in the tag.
@@ -231,19 +236,13 @@ The command refuses to publish unless all of these conditions are true:
231
236
  - staged package names and versions match the selected release
232
237
  - the target version has not already been accepted by npm
233
238
 
234
- For `0.x` releases the default npm dist-tag is:
235
-
236
- ```text
237
- next
238
- ```
239
-
240
- For `1.x` and later it defaults to:
239
+ Stable BCP releases use this npm dist-tag by default, including stable `0.x` releases:
241
240
 
242
241
  ```text
243
242
  latest
244
243
  ```
245
244
 
246
- Override the tag explicitly when needed:
245
+ Use `BCP_DIST_TAG` only when intentionally publishing a separate channel such as `next` or `beta`:
247
246
 
248
247
  ```bash
249
248
  BCP_DIST_TAG=beta npm run release:publish:yes
@@ -258,6 +257,8 @@ npm run release:publish:yes
258
257
 
259
258
  The framework publishes first. After `npm publish` succeeds, the release script accepts either normal version visibility or the selected dist-tag pointing at the new version. This prevents npm registry/security-processing delays from being misclassified as a failed publish.
260
259
 
260
+ Using `latest` for stable releases is also part of the updater contract: `bcp update` resolves `@chidchanun/bcp@latest` by default.
261
+
261
262
  ## 10. Recover from a partial publish
262
263
 
263
264
  If the framework package was accepted by npm but publishing `create-bcp-app` failed, fix the external issue without changing that release commit or tag, then use:
@@ -270,18 +271,32 @@ Resume mode intentionally does not run `release:version-check` or `npm publish -
270
271
 
271
272
  Do not use resume mode to overwrite or replace an existing npm version; npm versions are immutable.
272
273
 
273
- ## 11. Install the preview release
274
+ ## 11. Install or update the stable release
275
+
276
+ The recommended new-project path is:
277
+
278
+ ```bash
279
+ npx create-bcp-app@latest my-app
280
+ ```
281
+
282
+ A generated application keeps the documented `bcp` import name through an npm alias and pins the selected framework version exactly.
283
+
284
+ For an existing project already on an updater-capable release:
285
+
286
+ ```bash
287
+ npm run update
288
+ ```
274
289
 
275
- The recommended path is the generator:
290
+ For a project on BCP 0.1.9 or older, bootstrap the updater once with:
276
291
 
277
292
  ```bash
278
- npx create-bcp-app@next my-app
293
+ npx @chidchanun/bcp@latest update
279
294
  ```
280
295
 
281
- For a `0.x` release published with the default `next` dist-tag, a manual install that preserves the `bcp` import name is:
296
+ A manual install that preserves the `bcp` import name is:
282
297
 
283
298
  ```bash
284
- npm install bcp@npm:@chidchanun/bcp@next react react-dom
299
+ npm install bcp@npm:@chidchanun/bcp@latest react react-dom
285
300
  ```
286
301
 
287
302
  Application code then continues using:
@@ -292,7 +307,7 @@ import {
292
307
  } from "bcp";
293
308
  ```
294
309
 
295
- The scoped package can also be installed directly as `@chidchanun/bcp@next`, but applications using the framework's documented `bcp` import path should prefer the alias form above.
310
+ The scoped package can also be installed directly as `@chidchanun/bcp@latest`, but applications using the framework's documented `bcp` import path should prefer the alias form above.
296
311
 
297
312
  ## 12. Trusted publishing
298
313
 
@@ -0,0 +1,240 @@
1
+ # Protected Route Guards
2
+
3
+ BCP route guards provide server-side protection for pages and page subtrees without repeating authentication checks in every `loader.ts`.
4
+
5
+ A guard lives in `guard.ts` or `guard.tsx` inside the `app/` tree. It protects pages in that directory and descendant route directories.
6
+
7
+ ```text
8
+ app/
9
+ └─ dashboard/
10
+ ├─ guard.ts
11
+ ├─ page.tsx
12
+ └─ users/
13
+ └─ [id]/
14
+ ├─ loader.ts
15
+ └─ page.tsx
16
+ ```
17
+
18
+ ## Authentication guard
19
+
20
+ ```ts
21
+ // app/dashboard/guard.ts
22
+ import {
23
+ getSession,
24
+ redirect,
25
+ } from "bcp/server";
26
+
27
+ export async function guard() {
28
+ const session =
29
+ await getSession<{
30
+ userId: number;
31
+ email: string;
32
+ role: string;
33
+ }>();
34
+
35
+ if (!session) {
36
+ return redirect(
37
+ "/login",
38
+ 303
39
+ );
40
+ }
41
+
42
+ return {
43
+ session,
44
+ role:
45
+ session.role,
46
+ };
47
+ }
48
+ ```
49
+
50
+ The guard runs before the route loader and page render. Returning a Web `Response`, including `redirect()`, stops the pipeline immediately.
51
+
52
+ ## Guard context
53
+
54
+ A guard receives:
55
+
56
+ ```ts
57
+ interface GuardContext {
58
+ params: Record<string, string | string[] | undefined>;
59
+ searchParams: URLSearchParams;
60
+ parentData: Readonly<Record<string, unknown>>;
61
+ }
62
+ ```
63
+
64
+ `params` contains the matched route params and `searchParams` represents the target page URL. `parentData` contains the JSON-safe values returned by ancestor guards.
65
+
66
+ Example nested role guard:
67
+
68
+ ```ts
69
+ // app/dashboard/admin/guard.ts
70
+ import {
71
+ redirect,
72
+ } from "bcp/server";
73
+
74
+ export async function guard({
75
+ parentData,
76
+ }) {
77
+ if (
78
+ parentData.role !==
79
+ "admin"
80
+ ) {
81
+ return redirect(
82
+ "/dashboard",
83
+ 303
84
+ );
85
+ }
86
+
87
+ return {
88
+ section:
89
+ "admin",
90
+ };
91
+ }
92
+ ```
93
+
94
+ Guards execute from the app root toward the page directory. Later guards receive the merged output of earlier guards.
95
+
96
+ ## Using guard data in a loader
97
+
98
+ Loaders receive the final merged guard data as `guardData`:
99
+
100
+ ```ts
101
+ // app/dashboard/users/[id]/loader.ts
102
+ export async function loader({
103
+ params,
104
+ guardData,
105
+ }) {
106
+ return {
107
+ id:
108
+ params.id,
109
+ currentUser:
110
+ guardData.session,
111
+ role:
112
+ guardData.role,
113
+ };
114
+ }
115
+ ```
116
+
117
+ This avoids calling `getSession()` again after the guard has already validated the request.
118
+
119
+ ## Using guard data in a page
120
+
121
+ Pages can read the same serialized guard result with `useGuardData<T>()`:
122
+
123
+ ```tsx
124
+ "use client";
125
+
126
+ import {
127
+ useGuardData,
128
+ } from "bcp";
129
+
130
+ export default function DashboardPage() {
131
+ const guard =
132
+ useGuardData<{
133
+ session: {
134
+ userId: number;
135
+ email: string;
136
+ };
137
+ role: string;
138
+ }>();
139
+
140
+ return (
141
+ <main>
142
+ Signed in as {
143
+ guard.session.email
144
+ }
145
+ </main>
146
+ );
147
+ }
148
+ ```
149
+
150
+ Guard data is available during SSR, hydration and SPA navigation. A guarded page does not need a `loader.ts` just to expose guard data.
151
+
152
+ ## Serialization rules
153
+
154
+ Guard return values must be JSON-safe because they are passed to loaders and, when the page reads them, serialized into framework navigation/SSR data.
155
+
156
+ Supported values include:
157
+
158
+ - `null`
159
+ - strings
160
+ - booleans
161
+ - finite numbers
162
+ - arrays containing supported values
163
+ - plain objects containing supported values
164
+
165
+ Unsupported values include functions, symbols, BigInt, non-finite numbers, class instances, Date/Map/Set objects and circular references.
166
+
167
+ Do not place passwords, raw authentication secrets, private keys or other sensitive server-only secrets in guard data. Guard data may be serialized to the browser when the page uses the route framework data pipeline.
168
+
169
+ ## Cookies and sessions
170
+
171
+ Guards run inside the normal BCP request context, so they can use:
172
+
173
+ ```ts
174
+ import {
175
+ cookies,
176
+ getSession,
177
+ headers,
178
+ requestId,
179
+ requestMethod,
180
+ requestUrl,
181
+ } from "bcp/server";
182
+ ```
183
+
184
+ Cookies set while an allowed guard runs are preserved on the final document or SPA navigation response. Cookies set before a guard redirect are preserved on that redirect as well.
185
+
186
+ ## Direct requests and SPA navigation
187
+
188
+ The same guard rules apply to:
189
+
190
+ - direct browser requests
191
+ - `<Link>` navigation
192
+ - `navigate()`
193
+ - `router.push()`
194
+ - `router.replace()`
195
+ - browser history navigation
196
+ - loader-backed SPA navigation
197
+
198
+ For SPA navigation, the guard evaluates the target page URL rather than the internal `/_bcp/navigation` transport URL.
199
+
200
+ A guard redirect is converted to a navigation redirect payload when appropriate so same-origin navigation can continue through the BCP router while preserving response cookies.
201
+
202
+ ## Middleware and guard order
203
+
204
+ Production request order is intentionally layered:
205
+
206
+ ```text
207
+ Security gateway
208
+
209
+ Response cache gateway
210
+
211
+ Project middleware
212
+
213
+ Route guard
214
+
215
+ Page loader
216
+
217
+ SSR / navigation payload
218
+ ```
219
+
220
+ Project middleware therefore remains the outer application interception layer. Route guards are intended for page authorization and route-specific access policy.
221
+
222
+ ## Cache safety
223
+
224
+ Pages protected by route guards are excluded from the automatic production page response cache. Guard results may depend on sessions, cookies, roles or user-specific state, so caching a guarded document without a user-aware key would be unsafe.
225
+
226
+ Applications can still use explicit server data caching where the cache key and invalidation strategy are safe for the data being cached.
227
+
228
+ ## Internal transport hardening
229
+
230
+ Standalone production transports evaluated guard data to the inner page runtime through a private framework header. Incoming user-supplied values for that header are removed before guard evaluation and before proxying so a browser cannot forge a successful guard result.
231
+
232
+ The serialized internal guard payload is size-limited. Applications should return only the small identity/authorization data required by descendant guards, loaders and pages.
233
+
234
+ ## Guard versus middleware
235
+
236
+ Use a route guard when access policy belongs to a page subtree and should integrate directly with loaders and page data.
237
+
238
+ Use middleware when a request-wide concern needs to run before route execution, such as rewrites, global redirects, shared request policy or non-page request interception.
239
+
240
+ Both can be used together; middleware runs before route guards.