@alxia/react-router 0.0.0-stage → 0.1.0

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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +289 -2
  3. package/dist/assets.d.ts +12 -0
  4. package/dist/assets.d.ts.map +1 -0
  5. package/dist/chunks/index-kfekhvgr.js +31 -0
  6. package/dist/chunks/index-kfekhvgr.js.map +10 -0
  7. package/dist/cli/index.d.ts +3 -0
  8. package/dist/cli/index.d.ts.map +1 -0
  9. package/dist/cli/index.js +160 -0
  10. package/dist/cli/index.js.map +13 -0
  11. package/dist/cli/main.d.ts +9 -0
  12. package/dist/cli/main.d.ts.map +1 -0
  13. package/dist/cli/reveal.d.ts +18 -0
  14. package/dist/cli/reveal.d.ts.map +1 -0
  15. package/dist/cli/template.d.ts +8 -0
  16. package/dist/cli/template.d.ts.map +1 -0
  17. package/dist/context.d.ts +70 -0
  18. package/dist/context.d.ts.map +1 -0
  19. package/dist/index.d.ts +4 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +166 -0
  22. package/dist/index.js.map +13 -0
  23. package/dist/react-router.d.ts +61 -0
  24. package/dist/react-router.d.ts.map +1 -0
  25. package/dist/server.d.ts +99 -0
  26. package/dist/server.d.ts.map +1 -0
  27. package/dist/vite/config.d.ts +33 -0
  28. package/dist/vite/config.d.ts.map +1 -0
  29. package/dist/vite/dev.d.ts +23 -0
  30. package/dist/vite/dev.d.ts.map +1 -0
  31. package/dist/vite/entry.d.ts +20 -0
  32. package/dist/vite/entry.d.ts.map +1 -0
  33. package/dist/vite/index.d.ts +33 -0
  34. package/dist/vite/index.d.ts.map +1 -0
  35. package/dist/vite/index.js +391 -0
  36. package/dist/vite/index.js.map +16 -0
  37. package/dist/vite/node.d.ts +16 -0
  38. package/dist/vite/node.d.ts.map +1 -0
  39. package/dist/vite/preview.d.ts +8 -0
  40. package/dist/vite/preview.d.ts.map +1 -0
  41. package/dist/vite/runtime.d.ts +8 -0
  42. package/dist/vite/runtime.d.ts.map +1 -0
  43. package/dist/vite/socket.d.ts +68 -0
  44. package/dist/vite/socket.d.ts.map +1 -0
  45. package/docs/README.md +12 -0
  46. package/docs/guide.md +843 -0
  47. package/docs/roadmap.md +96 -0
  48. package/docs/troubleshooting.md +832 -0
  49. package/package.json +77 -5
@@ -0,0 +1,832 @@
1
+ # Troubleshooting
2
+
3
+ Each entry is headed by the text you see: an error thrown at startup or in
4
+ a loader, a message React Router or the browser prints, or an error from
5
+ `tsc`. Paths, route ids and types in a message are the app's own, written
6
+ `…` below. A trap that prints nothing is under [Traps](#traps), by symptom.
7
+
8
+ **Thrown or printed**
9
+
10
+ - [`alxia-react-router: … is running on Node, and alxia's server runs on Bun. …`](#alxia-react-router--is-running-on-node-and-alxias-server-runs-on-bun-)
11
+ - [`ReferenceError: Bun is not defined`](#referenceerror-bun-is-not-defined)
12
+ - [`alxiaOf(): this request has no alxia context. …`](#alxiaof-this-request-has-no-alxia-context-)
13
+ - [`Error: No value found for context`](#error-no-value-found-for-context)
14
+ - [`alxia-react-router: … must export createServer() from @alxia/react-router as its default export: …`](#alxia-react-router--must-export-createserver-from-alxiareact-router-as-its-default-export-)
15
+ - [`alxia-react-router: the entry … does not exist. …`](#alxia-react-router-the-entry--does-not-exist-)
16
+ - [`alxia-react-router: React Router's Vite plugin is not in this config. …`](#alxia-react-router-react-routers-vite-plugin-is-not-in-this-config-)
17
+ - [`alxia-react-router: serverBundles splits React Router's server build in several, and alxia serves one. …`](#alxia-react-router-serverbundles-splits-react-routers-server-build-in-several-and-alxia-serves-one-)
18
+ - [`alxia-react-router: Vite's ssr environment does not run modules in this process, so the server cannot be loaded.`](#alxia-react-router-vites-ssr-environment-does-not-run-modules-in-this-process-so-the-server-cannot-be-loaded)
19
+ - [`alxia-react-router: a WebSocket upgrade failed: …`](#alxia-react-router-a-websocket-upgrade-failed-)
20
+ - [`The React Router Vite plugin requires the use of a Vite config file`](#the-react-router-vite-plugin-requires-the-use-of-a-vite-config-file)
21
+ - [`No route matches URL "/assets/…"`](#no-route-matches-url-assets)
22
+ - [``You made a POST request to "/" but did not provide an `action` for route "root", so there is no way to handle the request.``](#you-made-a-post-request-to--but-did-not-provide-an-action-for-route-root-so-there-is-no-way-to-handle-the-request)
23
+ - [`TypeError: reactRouter(): client is …, which is not a directory. …`](#typeerror-reactrouter-client-is--which-is-not-a-directory-)
24
+ - [`TypeError: reactRouter(): … cannot be served at a path of its own name; rename it. …`](#typeerror-reactrouter--cannot-be-served-at-a-path-of-its-own-name-rename-it-)
25
+ - [`Refused to execute inline script because it violates the following Content Security Policy directive: "default-src 'none'"`](#refused-to-execute-inline-script-because-it-violates-the-following-content-security-policy-directive-default-src-none)
26
+ - [`alxia-react-router: … already exists, and reveal leaves it as it is. Run alxia-react-router reveal --force to overwrite it.`](#alxia-react-router--already-exists-and-reveal-leaves-it-as-it-is-run-alxia-react-router-reveal---force-to-overwrite-it)
27
+ - [`alxia-react-router: no vite.config.ts in …. Run reveal from the app's root, beside vite.config.ts.`](#alxia-react-router-no-viteconfigts-in--run-reveal-from-the-apps-root-beside-viteconfigts)
28
+ - [`alxia-react-router: … gives alxia() an entry reveal cannot read. …`](#alxia-react-router--gives-alxia-an-entry-reveal-cannot-read-)
29
+ - [`alxia-react-router: … computes appDirectory, which reveal cannot read. …`](#alxia-react-router--computes-appdirectory-which-reveal-cannot-read-)
30
+ - [`alxia-react-router: unknown command ….`](#alxia-react-router-unknown-command-)
31
+ - [`alxia-react-router: unknown option … for reveal.`](#alxia-react-router-unknown-option--for-reveal)
32
+ - [`error: GET https://registry.npmjs.org/alxia-react-router - 404`](#error-get-httpsregistrynpmjsorgalxia-react-router---404)
33
+ - [`alxia-react-router: build/server/index.js does not exist. Run react-router build before vite preview.`](#alxia-react-router-buildserverindexjs-does-not-exist-run-react-router-build-before-vite-preview)
34
+ - [`alxia-react-router: build/server/index.js is not alxia's server: its default export has no fetch. …`](#alxia-react-router-buildserverindexjs-is-not-alxias-server-its-default-export-has-no-fetch-)
35
+ - [`warn: incorrect peer dependency "typescript@5.9.3"`](#warn-incorrect-peer-dependency-typescript593)
36
+
37
+ **Types**
38
+
39
+ - [`Property '…' does not exist on type 'BaseContext & …'`](#property--does-not-exist-on-type-basecontext--)
40
+ - [`Type '…' does not satisfy the constraint 'AnyAlxia | ReactRouterServer<AnyAlxia>'`](#type--does-not-satisfy-the-constraint-anyalxia--reactrouterserveranyalxia)
41
+ - [`Type '(app: …) => void' is not assignable to type '(app: …) => AnyAlxia'`](#type-app---void-is-not-assignable-to-type-app---anyalxia)
42
+ - [`Property '…' does not exist on type 'BaseContext & { readonly 'Register.server must be typeof server, the default export of createServer()': never; }'`](#property--does-not-exist-on-type-basecontext---readonly-registerserver-must-be-typeof-server-the-default-export-of-createserver-never-)
43
+ - [`Subsequent property declarations must have the same type. Property 'server' must be of type …`](#subsequent-property-declarations-must-have-the-same-type-property-server-must-be-of-type-)
44
+
45
+ **Traps**
46
+
47
+ - [`bun build/server/index.js` exits at once, printing nothing](#bun-buildserverindexjs-exits-at-once-printing-nothing)
48
+ - [A loader reads `null` from the app's own key](#a-loader-reads-null-from-the-apps-own-key)
49
+ - [A page answers alxia's JSON 404 or 405 instead of rendering](#a-page-answers-alxias-json-404-or-405-instead-of-rendering)
50
+ - [A streamed page arrives in one piece](#a-streamed-page-arrives-in-one-piece)
51
+ - [The logger times a streamed page at a few milliseconds](#the-logger-times-a-streamed-page-at-a-few-milliseconds)
52
+ - [`ctx.server` is `undefined` under `react-router dev`](#ctxserver-is-undefined-under-react-router-dev)
53
+ - [A `publish` under `react-router dev` misses the sockets opened before an edit](#a-publish-under-react-router-dev-misses-the-sockets-opened-before-an-edit)
54
+
55
+ ## Thrown or printed
56
+
57
+ ### `alxia-react-router: … is running on Node, and alxia's server runs on Bun. …`
58
+
59
+ **When:** `bun run dev`, `vite preview`, or a `react-router build` that
60
+ prerenders, on a machine where a node is installed. The full message under
61
+ `react-router dev`:
62
+
63
+ ```
64
+ alxia-react-router: react-router dev is running on Node, and alxia's server runs on Bun. Add a bunfig.toml beside package.json with "[run]" and "bun = true", so bun run starts it on Bun, or run it as bun --bun react-router dev.
65
+ ```
66
+
67
+ **Why:** the `react-router` and `vite` CLIs start with
68
+ `#!/usr/bin/env node`. `bun run` honours that line when a node is on the
69
+ `PATH`, so the dev server, and alxia's server inside it, run on Node,
70
+ where `Bun` does not exist.
71
+
72
+ **Fix:** a `bunfig.toml` beside `package.json`, so `bun run` starts every
73
+ script on Bun:
74
+
75
+ ```toml
76
+ [run]
77
+ bun = true
78
+ ```
79
+
80
+ Or run the one command on Bun: `bun --bun react-router dev`. Under
81
+ `vite preview` or a prerendering build, the message begins
82
+ `alxia-react-router: vite preview, or a react-router build that prerenders, is running on Node`
83
+ and names `bun --bun vite preview` and `bun --bun react-router build`.
84
+
85
+ ### `ReferenceError: Bun is not defined`
86
+
87
+ **When:** the first request to a page, with a stack through the app's
88
+ own code or alxia's, from a server running on Node that the plugin did
89
+ not start, so it could not refuse it: `node build/server/index.js`, or a
90
+ `start` script that runs one.
91
+
92
+ **Fix:** run the server on Bun: `bun build/server/index.js`. Under
93
+ `react-router dev` or `vite preview`, the plugin refuses Node at startup
94
+ with [the entry above](#alxia-react-router--is-running-on-node-and-alxias-server-runs-on-bun-).
95
+
96
+ ### `alxiaOf(): this request has no alxia context. …`
97
+
98
+ ```text
99
+ Error: alxiaOf(): this request has no alxia context. Serve the React Router app through alxia: add alxia() from @alxia/react-router/vite to vite.config.ts's plugins, or, with a server of your own, serve the build through reactRouter() from @alxia/react-router.
100
+ ```
101
+
102
+ **When:** a loader calls `alxiaOf` and `alxiaContext` was not set.
103
+
104
+ **Why:** one of three:
105
+
106
+ - the request did not go through alxia: `vite.config.ts` has no `alxia()`,
107
+ the build runs under `react-router-serve`, or a unit test calls the
108
+ loader with a bare `RouterContextProvider`;
109
+ - a server of your own, without the plugin, serves the build without
110
+ `reactRouter()`;
111
+ - a server of your own, without the plugin, in a monorepo: the server
112
+ build holds **its own copy** of `@alxia/react-router`. Vite leaves a
113
+ package external only when it resolves under `node_modules` to a `.js`
114
+ file. A package linked from a workspace (`workspace:^`, `bun link`)
115
+ resolves to its folder, outside `node_modules`, and Vite bundles it into
116
+ `build/server/index.js` with a second `alxiaContext` the server never
117
+ sets.
118
+
119
+ **Fix:** add `alxia()` to `vite.config.ts`, as in the
120
+ [setup](guide.md#setup). With a server of your own in a monorepo, tell
121
+ Vite to leave the package external:
122
+
123
+ ```ts
124
+ // vite.config.ts
125
+ import { reactRouter } from '@react-router/dev/vite';
126
+ import { defineConfig } from 'vite';
127
+
128
+ export default defineConfig({
129
+ ssr: { external: ['@alxia/react-router'] },
130
+ plugins: [reactRouter()],
131
+ });
132
+ ```
133
+
134
+ `grep '@alxia/react-router' build/server/index.js` should show an
135
+ `import … from "@alxia/react-router"` line, not the package's code. In a
136
+ unit test, set the key yourself:
137
+
138
+ ```ts
139
+ import { alxiaContext } from '@alxia/react-router';
140
+ import { RouterContextProvider } from 'react-router';
141
+
142
+ const context = new RouterContextProvider();
143
+ context.set(alxiaContext, { user: { name: 'Ada' } });
144
+ await loader({ context, request: new Request('http://localhost/'), params: {} } as never);
145
+ ```
146
+
147
+ ### `Error: No value found for context`
148
+
149
+ **When:** a loader, action or middleware calls `context.get(key)` with a
150
+ key made by `createContext()` with no default value, in a file under
151
+ `app/`, and a server of your own, without the plugin, set it in
152
+ `getLoadContext`.
153
+
154
+ **Why:** `react-router build` bundles `app/context.ts` into
155
+ `build/server/index.js`. A server outside the build, which imports
156
+ `app/context.ts` itself, holds another `createContext()` object. React
157
+ Router matches keys by identity, so that server's
158
+ `context.set(userContext, user)` sets a key the loaders never read.
159
+
160
+ ```ts
161
+ // server.ts, without the plugin: the trap
162
+ import { userContext } from './app/context'; // not the build's copy
163
+ reactRouter(app, { build, getLoadContext: ({ user }, context) => context.set(userContext, user) });
164
+ ```
165
+
166
+ **Fix:** serve the app with the plugin, `alxia()` in `vite.config.ts`: the
167
+ server is then built with the routes, and `app/context.ts` is one module
168
+ for both, in dev and in the build. See
169
+ [the app's own context keys](guide.md#the-apps-own-context-keys).
170
+
171
+ Without the plugin, read alxia's context with `alxiaOf`: its key,
172
+ `alxiaContext`, lives in this package under `node_modules`, which Vite
173
+ leaves external, so the build and the server load one module. A key
174
+ exported by a package installed under `node_modules` (`@acme/session`)
175
+ works for the same reason.
176
+
177
+ ### `alxia-react-router: … must export createServer() from @alxia/react-router as its default export: …`
178
+
179
+ ```text
180
+ TypeError: alxia-react-router: app/server.ts must export createServer() from @alxia/react-router as its default export: export default createServer({ … }).
181
+ ```
182
+
183
+ Under `react-router dev`, Vite's error page shows it, and the request is a
184
+ 500. In a build, `bun build/server/index.js` throws it at startup.
185
+
186
+ **When:** the server file has no default export, or its default is not
187
+ what `createServer()` returns: an alxia app, a named `export const server`,
188
+ or the options object itself.
189
+
190
+ **Why:** the plugin makes the app with the default export's `create`, and
191
+ listens with its `start`.
192
+
193
+ **Fix:** export the server as the default:
194
+
195
+ ```ts
196
+ // app/server.ts
197
+ import { createServer } from '@alxia/react-router';
198
+
199
+ const server = createServer({ configure: (app) => app });
200
+ export default server;
201
+ ```
202
+
203
+ An alxia app of your own, with `reactRouter()` on it, is served without the
204
+ plugin: see [a server of your own](guide.md#a-server-of-your-own-without-the-plugin).
205
+
206
+ ### `alxia-react-router: the entry … does not exist. …`
207
+
208
+ ```text
209
+ Error: alxia-react-router: the entry server/main.ts does not exist. Create it, or leave entry out to use app/server.ts, or the default server without it.
210
+ ```
211
+
212
+ **When:** `alxia({ entry })` names a file that is not there, relative to
213
+ Vite's root. Under `react-router dev` each request is a 500 saying so;
214
+ `react-router build` fails.
215
+
216
+ **Why:** a missing `app/server.ts` falls back to the default server, but an
217
+ `entry` you named is taken as meant.
218
+
219
+ **Fix:** correct the path, or leave `entry` out.
220
+
221
+ ### `alxia-react-router: React Router's Vite plugin is not in this config. …`
222
+
223
+ ```text
224
+ Error: alxia-react-router: React Router's Vite plugin is not in this config. Add reactRouter() from @react-router/dev/vite beside alxia() in vite.config.ts.
225
+ ```
226
+
227
+ **When:** Vite starts, for `react-router dev`, `react-router build` or
228
+ `vite`, with `alxia()` and no `reactRouter()`.
229
+
230
+ **Why:** the plugin reads React Router's config (the app directory, the
231
+ build directory, the server build's file name) from React Router's own
232
+ plugin.
233
+
234
+ **Fix:** list both, in any order:
235
+
236
+ ```ts
237
+ // vite.config.ts
238
+ import { alxia } from '@alxia/react-router/vite';
239
+ import { reactRouter } from '@react-router/dev/vite';
240
+ import { defineConfig } from 'vite';
241
+
242
+ export default defineConfig({ plugins: [reactRouter(), alxia()] });
243
+ ```
244
+
245
+ ### `alxia-react-router: serverBundles splits React Router's server build in several, and alxia serves one. …`
246
+
247
+ ```text
248
+ Error: alxia-react-router: serverBundles splits React Router's server build in several, and alxia serves one. Remove serverBundles from react-router.config.ts.
249
+ ```
250
+
251
+ **When:** `react-router.config.ts` sets `serverBundles`.
252
+
253
+ **Why:** the plugin builds one server, `build/server/index.js`, with React
254
+ Router's build inside it. Server bundles make several, each its own
255
+ `index.js`.
256
+
257
+ **Fix:** remove `serverBundles`. One alxia server serves every route.
258
+
259
+ ### `alxia-react-router: Vite's ssr environment does not run modules in this process, so the server cannot be loaded.`
260
+
261
+ **When:** under `react-router dev`, Vite's `ssr` environment is not a
262
+ runnable one: another plugin replaced it with an environment that runs
263
+ elsewhere, such as a worker runtime.
264
+
265
+ **Why:** the plugin loads the server with the `ssr` environment's module
266
+ runner, in Vite's own process, where Bun runs the app. The check reads the
267
+ environment's `runner` itself, so an app whose Vite is another copy than
268
+ the one this package was tested with passes it.
269
+
270
+ **Fix:** drop the plugin that replaces the `ssr` environment. alxia runs
271
+ under Bun, not in a worker runtime.
272
+
273
+ ### `alxia-react-router: a WebSocket upgrade failed: …`
274
+
275
+ Printed by Vite's logger, followed by the error's stack.
276
+
277
+ **When:** under `react-router dev`, a client opens a WebSocket to the app,
278
+ and loading the server throws: `app/server.ts`, or a module it imports,
279
+ throws when it runs or does not compile, or the plugin itself fails (the
280
+ prefix is then followed by its own message, such as `Vite's ssr
281
+ environment does not run modules in this process`). Under `vite preview`,
282
+ the built server failed to load: the prefix is followed by
283
+ [`… does not exist. Run react-router build before vite preview.`](#alxia-react-router-buildserverindexjs-does-not-exist-run-react-router-build-before-vite-preview)
284
+ or [`… is not alxia's server: …`](#alxia-react-router-buildserverindexjs-is-not-alxias-server-its-default-export-has-no-fetch-),
285
+ whose fixes apply. The client's handshake gets a `500`, and no socket.
286
+
287
+ **Why:** the plugin loads the server, through Vite's SSR runner, on each
288
+ upgrade as on each request, so that an edit is used from the next
289
+ connection. An HTTP request with the same error goes to Vite's error
290
+ page; an upgrade has no page to show, so the error is printed.
291
+
292
+ **Fix:** fix the error the stack names. The next connection loads the
293
+ server again, with no restart.
294
+
295
+ ### `The React Router Vite plugin requires the use of a Vite config file`
296
+
297
+ React Router's Vite plugin throws it.
298
+
299
+ **When:** a script or a test starts Vite with `createServer({
300
+ configFile: false, plugins: [alxia(), reactRouter()] })`.
301
+
302
+ **Why:** React Router's plugin reads its options from a config file, and
303
+ refuses inline ones.
304
+
305
+ **Fix:** write the config to a file, and point `configFile` at it:
306
+
307
+ ```ts
308
+ import { createServer } from 'vite';
309
+
310
+ const server = await createServer({
311
+ root,
312
+ configFile: `${root}/vite.config.ts`,
313
+ server: { port: 0 },
314
+ });
315
+ await server.listen();
316
+ ```
317
+
318
+ ### `No route matches URL "/assets/…"`
319
+
320
+ React Router's 404 page answers every file under `/assets`.
321
+
322
+ **When:** in a build, the server runs in `development`: `createServer({
323
+ mode: 'development' })`, or a server of your own with `reactRouter(app, {
324
+ mode: 'development' })`.
325
+
326
+ **Why:** in `development` the client's files are left to Vite's dev
327
+ server, so nothing serves `build/client`, and the catch-all gets the
328
+ assets. Under the plugin the mode follows the command, `production` in a
329
+ build, whatever `NODE_ENV` says; only an explicit `mode` changes it.
330
+
331
+ **Fix:** leave `mode` out, or set it from where the server runs:
332
+
333
+ ```ts
334
+ // server.ts, without the plugin
335
+ reactRouter(app, { build, client: 'build/client', mode: 'production' });
336
+ ```
337
+
338
+ ### ``You made a POST request to "/" but did not provide an `action` for route "root", so there is no way to handle the request.``
339
+
340
+ React Router prints it, and answers `405`.
341
+
342
+ **When:** a `POST /` to an app whose index route has the action.
343
+
344
+ **Why:** a `POST /` is the root route's. The index route's action is
345
+ `POST /?index`; React Router's own `<Form>` adds `?index` when it posts
346
+ from an index route. This is React Router's behaviour, not alxia's.
347
+
348
+ **Fix:** post to `/?index`, as `<Form method="post">` does, or in a test:
349
+
350
+ ```ts
351
+ await app.request('/?index', { method: 'POST', body: new URLSearchParams({ step: '2' }) });
352
+ ```
353
+
354
+ The single-fetch form is `POST /_.data?index`.
355
+
356
+ ### `TypeError: reactRouter(): client is …, which is not a directory. …`
357
+
358
+ ```text
359
+ TypeError: reactRouter(): client is build/clinet, which is not a directory. Pass the client build, build/client by default.
360
+ ```
361
+
362
+ **When:** at startup, `client` — `createServer`'s, `create`'s or
363
+ `reactRouter`'s — names a path that is missing or a file.
364
+
365
+ **Why:** the folder is read when the app is made, to declare a route per
366
+ top-level entry. A relative path is resolved against the process's
367
+ working directory, not the server file. Under the plugin, with no
368
+ `client` option, the folder is `build/client` resolved against the built
369
+ file, and is found wherever the process starts.
370
+
371
+ **Fix:** build first, leave `client` to the plugin, or pass the folder
372
+ relative to the file that names it:
373
+
374
+ ```ts
375
+ reactRouter(app, { build, client: new URL('./build/client', import.meta.url) });
376
+ ```
377
+
378
+ ### `TypeError: reactRouter(): … cannot be served at a path of its own name; rename it. …`
379
+
380
+ **When:** at startup, a top-level file or folder of the client build — a
381
+ copy of `public/` — has a name no route can carry: `a:b.txt` (a `:` starts
382
+ a parameter), `*x` (a `*` is a wildcard). The message ends with the core's
383
+ refusal, which says which.
384
+
385
+ A name a URL only encodes is fine: `my file.pdf` is served at
386
+ `/my%20file.pdf`, `café.png` at `/caf%C3%A9.png`, as a browser asks for
387
+ them.
388
+
389
+ **Fix:** rename the file in `public/`, or move it into a folder: a file
390
+ inside a folder is served by that folder's `static` route, whatever its
391
+ name.
392
+
393
+ ### `Refused to execute inline script because it violates the following Content Security Policy directive: "default-src 'none'"`
394
+
395
+ The browser's console prints it; the page renders on the server, then
396
+ never hydrates, and a `<Form>` does not post (`form-action 'none'`).
397
+
398
+ **When:** `@alxia/secure-headers` is used with its default policy.
399
+
400
+ **Why:** the default, `default-src 'none'; base-uri 'none'; form-action
401
+ 'none'; frame-ancestors 'none'`, suits an API. A page loads its bundles
402
+ from `/assets`, runs React Router's inline scripts, and posts forms.
403
+
404
+ **Fix:** give the app a policy that allows the page's own scripts:
405
+
406
+ ```ts
407
+ // app/server.ts
408
+ import { createServer } from '@alxia/react-router';
409
+ import { secureHeaders } from '@alxia/secure-headers';
410
+
411
+ export default createServer({
412
+ configure: (app) =>
413
+ app.use(
414
+ secureHeaders({
415
+ contentSecurityPolicy:
416
+ "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; form-action 'self'; base-uri 'self'; frame-ancestors 'none'",
417
+ }),
418
+ ),
419
+ });
420
+ ```
421
+
422
+ or `contentSecurityPolicy: false`. A per-request nonce, shared with
423
+ `<Scripts nonce>`, would drop `'unsafe-inline'`; it is on the
424
+ [roadmap](roadmap.md).
425
+
426
+ ### `alxia-react-router: … already exists, and reveal leaves it as it is. Run alxia-react-router reveal --force to overwrite it.`
427
+
428
+ ```text
429
+ alxia-react-router: app/server.ts already exists, and reveal leaves it as it is. Run alxia-react-router reveal --force to overwrite it.
430
+ ```
431
+
432
+ **When:** `bunx alxia-react-router reveal` finds a server file where it
433
+ would write one: `app/server.ts`, or the file `alxia({ entry })` names. It
434
+ exits 1 and writes nothing.
435
+
436
+ **Why:** that file is the app's server, customised or not; reveal never
437
+ replaces it unasked.
438
+
439
+ **Fix:** keep it, or overwrite it with the default server, its options
440
+ commented:
441
+
442
+ ```sh
443
+ bunx alxia-react-router reveal --force
444
+ ```
445
+
446
+ ### `alxia-react-router: no vite.config.ts in …. Run reveal from the app's root, beside vite.config.ts.`
447
+
448
+ **When:** `bunx alxia-react-router reveal` runs in a folder with no
449
+ `vite.config.ts` (nor `.mts`, `.cts`, `.js`, `.mjs` or `.cjs`): a
450
+ subfolder of the app, or another project.
451
+
452
+ **Why:** reveal reads where to write from the app's `vite.config.ts`,
453
+ `alxia({ entry })`, and writes relative to it.
454
+
455
+ **Fix:** `cd` to the folder that holds `vite.config.ts`, and run it there.
456
+
457
+ ### `alxia-react-router: … gives alxia() an entry reveal cannot read. …`
458
+
459
+ ```text
460
+ alxia-react-router: vite.config.ts gives alxia() an entry reveal cannot read. Write it as a string literal, alxia({ entry: 'app/server.ts' }), and run reveal again.
461
+ ```
462
+
463
+ **When:** `vite.config.ts` passes `alxia()` an `entry` that is not a string
464
+ literal: a constant, a template with `${…}`, a call. Reveal exits 1 and
465
+ writes nothing.
466
+
467
+ **Why:** reveal reads the config as text, cheaply, and does not run it.
468
+ Guessing `app/server.ts` would write a file the plugin does not load.
469
+
470
+ **Fix:** write the path as a literal, run reveal, then compute it again if
471
+ you need to:
472
+
473
+ ```ts
474
+ // vite.config.ts
475
+ import { alxia } from '@alxia/react-router/vite';
476
+ import { reactRouter } from '@react-router/dev/vite';
477
+ import { defineConfig } from 'vite';
478
+
479
+ export default defineConfig({ plugins: [reactRouter(), alxia({ entry: 'server/main.ts' })] });
480
+ ```
481
+
482
+ ### `alxia-react-router: … computes appDirectory, which reveal cannot read. …`
483
+
484
+ ```text
485
+ alxia-react-router: react-router.config.ts computes appDirectory, which reveal cannot read. Write it as a string literal, appDirectory: 'app', and run reveal again.
486
+ ```
487
+
488
+ **When:** `react-router.config.ts` sets `appDirectory` to something other
489
+ than a string literal, and `vite.config.ts` names no `entry`. Reveal exits
490
+ 1 and writes nothing.
491
+
492
+ **Why:** the same as above: reveal does not run the config, and `app/`
493
+ might not be the folder the plugin reads.
494
+
495
+ **Fix:** write `appDirectory` as a literal for the time of the reveal, or
496
+ name the file in `vite.config.ts`, `alxia({ entry: 'src/server.ts' })`.
497
+
498
+ ### `alxia-react-router: unknown command ….`
499
+
500
+ **When:** the bin is given a command other than `reveal`, such as
501
+ `bunx alxia-react-router reveal-server`. It prints its usage and exits 1.
502
+
503
+ **Fix:** `bunx alxia-react-router reveal`, or `--help` for the usage.
504
+
505
+ ### `alxia-react-router: unknown option … for reveal.`
506
+
507
+ **When:** `reveal` is given an option other than `--force` or `--help`,
508
+ such as react-router-hono-server's `reveal file` or `reveal folder`. It
509
+ prints its usage and exits 1.
510
+
511
+ **Fix:** `bunx alxia-react-router reveal` writes the one server file the
512
+ plugin reads. For a server in a folder of its own, name it first,
513
+ `alxia({ entry: 'app/server/index.ts' })`, then run `reveal`: it writes
514
+ there.
515
+
516
+ ### `error: GET https://registry.npmjs.org/alxia-react-router - 404`
517
+
518
+ **When:** `bunx alxia-react-router reveal` runs in a project where
519
+ `@alxia/react-router` is not installed.
520
+
521
+ **Why:** `bunx` looks for the bin in `node_modules/.bin`, then for an npm
522
+ package of the bin's name. The bin is `@alxia/react-router`'s, and no
523
+ package is named `alxia-react-router`.
524
+
525
+ **Fix:** install the package, then run the bin:
526
+
527
+ ```sh
528
+ bun add @alxia/core @alxia/react-router
529
+ bunx alxia-react-router reveal
530
+ ```
531
+
532
+ ### `alxia-react-router: build/server/index.js does not exist. Run react-router build before vite preview.`
533
+
534
+ With another `buildDirectory` or `serverBuildFile`, the message names
535
+ that file.
536
+
537
+ Under `vite preview`, every request is a 500 with this text, and Vite's
538
+ terminal prints it.
539
+
540
+ **When:** the preview server starts before a build, or after
541
+ `build/server/index.js` was removed.
542
+
543
+ **Why:** under the plugin, the preview serves the built server, and there
544
+ is none yet.
545
+
546
+ **Fix:** build, then preview:
547
+
548
+ ```sh
549
+ bun run build
550
+ bunx --bun vite preview
551
+ ```
552
+
553
+ The next request after the build loads it, with no restart.
554
+
555
+ ### `alxia-react-router: build/server/index.js is not alxia's server: its default export has no fetch. …`
556
+
557
+ With another `buildDirectory` or `serverBuildFile`, the message names
558
+ that file.
559
+
560
+ ```text
561
+ alxia-react-router: build/server/index.js is not alxia's server: its default export has no fetch. Build it with alxia() in vite.config.ts's plugins, then run vite preview again.
562
+ ```
563
+
564
+ **When:** under `vite preview`, the build in `build/` was made without
565
+ `alxia()`: before the plugin was added, or with another Vite config.
566
+
567
+ **Why:** React Router's own server build exports the routes and no app.
568
+ The plugin hands each request to the default export's `fetch`, which only
569
+ a build made with the plugin has.
570
+
571
+ **Fix:** add `alxia()` to `vite.config.ts`'s plugins, run `bun run build`,
572
+ then restart `vite preview`: a build already loaded is not read again.
573
+
574
+ ### `warn: incorrect peer dependency "typescript@5.9.3"`
575
+
576
+ `bun add @alxia/core @alxia/react-router` prints it in the official
577
+ template.
578
+
579
+ **Why:** the template ships TypeScript 5.9, and alxia's packages declare
580
+ `typescript` 6 or 7 as a peer: their declarations are tested with those.
581
+ The template's own code, with a server file and typed loaders, typechecks
582
+ under 5.9 too.
583
+
584
+ **Fix:** none is needed to run. To silence it, and typecheck with what
585
+ alxia is tested on:
586
+
587
+ ```sh
588
+ bun add -d typescript@^6
589
+ ```
590
+
591
+ ## Types
592
+
593
+ ### `Property '…' does not exist on type 'BaseContext & …'`
594
+
595
+ ```text
596
+ error TS2339: Property 'tenant' does not exist on type 'BaseContext & Empty & { requestId: string; log: RequestLog; } & { user: { name: string; } | null; }'.
597
+ ```
598
+
599
+ **When:** a loader reads, through `alxiaOf`, or `getLoadContext`
600
+ destructures, something no hook of the server derives. With the type
601
+ `'BaseContext & Empty'` alone, `alxiaOf(context)` has no server to read:
602
+ `app/server.ts` has no `Register` declaration, or there is no
603
+ `app/server.ts`.
604
+
605
+ **Why:** the type is the app's context at the point of the catch-all, as
606
+ for any route: "order is meaning".
607
+
608
+ **Fix:** derive it in `configure`, and register the server once:
609
+
610
+ ```ts
611
+ // app/server.ts
612
+ import { createServer } from '@alxia/react-router';
613
+
614
+ const server = createServer({
615
+ configure: (app) =>
616
+ app.derive(({ request }) => ({ tenant: request.headers.get('x-tenant') ?? 'default' })),
617
+ });
618
+ export default server;
619
+
620
+ declare module '@alxia/react-router' {
621
+ interface Register {
622
+ server: typeof server;
623
+ }
624
+ }
625
+ ```
626
+
627
+ ### `Type '…' does not satisfy the constraint 'AnyAlxia | ReactRouterServer<AnyAlxia>'`
628
+
629
+ ```text
630
+ error TS2344: Type '{ user: string; }' does not satisfy the constraint 'AnyAlxia | ReactRouterServer<AnyAlxia>'.
631
+ ```
632
+
633
+ **When:** `alxiaOf<T>()` is given a type that is neither a server nor an
634
+ alxia app, such as the context's own shape.
635
+
636
+ **Why:** the type argument is what the context is read from, as
637
+ `ContextOf<App>` reads an app.
638
+
639
+ **Fix:** pass the server's type, or register it and pass nothing:
640
+
641
+ ```ts
642
+ // app/server.ts
643
+ export type Server = typeof server;
644
+
645
+ // app/routes/home.tsx
646
+ import type { Server } from '../server';
647
+ const { user } = alxiaOf<Server>(context);
648
+ ```
649
+
650
+ ### `Type '(app: …) => void' is not assignable to type '(app: …) => AnyAlxia'`
651
+
652
+ ```text
653
+ error TS2322: Type '(app: FreshApp) => void' is not assignable to type '(app: FreshApp) => AnyAlxia'.
654
+ Type 'void' is not assignable to type 'AnyAlxia'.
655
+ ```
656
+
657
+ **When:** `configure` or `beforeAll` has a body that declares on `app` and
658
+ returns nothing.
659
+
660
+ **Why:** each returns the app it built: that return is how its types reach
661
+ the loaders and `getLoadContext`.
662
+
663
+ **Fix:** return the chain:
664
+
665
+ ```ts
666
+ createServer({
667
+ configure: (app) => app.get('/api/health', ({ reply }) => reply.ok({ ok: true })),
668
+ });
669
+ ```
670
+
671
+ ### `Property '…' does not exist on type 'BaseContext & { readonly 'Register.server must be typeof server, the default export of createServer()': never; }'`
672
+
673
+ ```text
674
+ error TS2339: Property 'user' does not exist on type 'BaseContext & { readonly 'Register.server must be typeof server, the default export of createServer()': never; }'.
675
+ ```
676
+
677
+ **When:** every `alxiaOf(context)` read fails with it: `Register`'s
678
+ `server` names something that is neither a server nor an alxia app, most
679
+ often the module rather than its default export,
680
+ `server: typeof import('./server')`.
681
+
682
+ **Why:** a wrong registration types the context as one marker key, so
683
+ that each read is a compile error rather than `never`, which would let
684
+ anything through.
685
+
686
+ **Fix:** name the default export's type:
687
+
688
+ ```ts
689
+ // app/server.ts
690
+ const server = createServer({ configure: (app) => app });
691
+ export default server;
692
+
693
+ declare module '@alxia/react-router' {
694
+ interface Register {
695
+ server: typeof server;
696
+ }
697
+ }
698
+ ```
699
+
700
+ ### `Subsequent property declarations must have the same type. Property 'server' must be of type …`
701
+
702
+ ```text
703
+ error TS2717: Subsequent property declarations must have the same type. Property 'server' must be of type 'ReactRouterServer<…>', but here has type 'ReactRouterServer<…>'.
704
+ ```
705
+
706
+ **When:** two files of one TypeScript program declare `Register`'s
707
+ `server`: two React Router apps under one tsconfig, or a copy of the
708
+ declaration left in a second file.
709
+
710
+ **Why:** a program has one `Register`, and it names one server: the one
711
+ whose catch-all every loader of the build runs behind.
712
+
713
+ **Fix:** keep one declaration per app, beside its server. Give each app of
714
+ a monorepo its own tsconfig, or drop `Register` and pass the type
715
+ argument, `alxiaOf<Server>(context)`.
716
+
717
+ ## Traps
718
+
719
+ ### `bun build/server/index.js` exits at once, printing nothing
720
+
721
+ **Why:** the build was made without the plugin, so `build/server/index.js`
722
+ is React Router's plain server build: a module of exports, which listens
723
+ on nothing. Or it was imported, not run: the server listens only when it
724
+ is the process's entry point.
725
+
726
+ **Fix:** add `alxia()` to `vite.config.ts` and build again; the start line
727
+ `alxia listening on <url>` then comes up. Run the file itself,
728
+ `bun build/server/index.js`, not through another module's `import`.
729
+
730
+ ### A loader reads `null` from the app's own key
731
+
732
+ The same cause as [`Error: No value found for context`](#error-no-value-found-for-context),
733
+ with a key that has a default: `createContext<User | null>(null)`. The
734
+ loader silently reads the default. Serve the app with the plugin, or read
735
+ the context through `alxiaOf`.
736
+
737
+ ### A page answers alxia's JSON 404 or 405 instead of rendering
738
+
739
+ `{"error":"not_found"}` or `{"error":"method_not_allowed"}` where a page
740
+ was expected.
741
+
742
+ **Why:** an alxia route's path covers the page's, and the core ranks it
743
+ first wherever it was declared: segment by segment, a literal beats a
744
+ parameter, which beats the catch-all's `/*`. Then the path is chosen
745
+ before the method. So:
746
+
747
+ - `GET /:slug` takes `/about`, and every other one-segment page;
748
+ - `POST /account` alone makes `GET /account` a 405, not the page;
749
+ - a folder of `public/`, `public/blog/`, becomes `static('/blog', …)`, and
750
+ `/blog/first-post` is its 404.
751
+
752
+ **Fix:** keep alxia's routes under a prefix the pages do not use, and
753
+ `public/`'s folders apart from the page paths:
754
+
755
+ ```ts
756
+ createServer({
757
+ configure: (app) =>
758
+ app.get('/api/posts/:slug', ({ params, reply }) => reply.ok({ slug: params.slug })),
759
+ });
760
+ ```
761
+
762
+ ### A streamed page arrives in one piece
763
+
764
+ The shell and its `<Suspense>` fallback should come first, and the
765
+ deferred value later. If the whole page comes at once:
766
+
767
+ - **The client is a bot to `isbot`.** React Router's entry waits for
768
+ `allReady` before it answers a bot, and `isbot('Bun/1.4.2')` is `true`:
769
+ Bun's `fetch`, `curl` and most test clients get the finished page. Send a
770
+ browser's user agent:
771
+
772
+ ```ts
773
+ const BROWSER = 'Mozilla/5.0 (Macintosh) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0 Safari/537.36';
774
+ await fetch(url, { headers: { 'user-agent': BROWSER } });
775
+ ```
776
+
777
+ - **A compressor holds the stream.** One that does not flush after each
778
+ chunk sends the page when it ends. `@alxia/compress` flushes a body with
779
+ no `Content-Length` as it comes, so the cause is elsewhere: a proxy or a
780
+ CDN in front of the server that buffers to compress. Compare with
781
+ `accept-encoding: identity`, then without the proxy.
782
+
783
+ ### The logger times a streamed page at a few milliseconds
784
+
785
+ `@alxia/logger` writes its entry when the response's headers leave, so a
786
+ page that streams for 800 ms is logged at its first byte. The duration is
787
+ the server's time to first byte, not the page's.
788
+
789
+ ### `ctx.server` is `undefined` under `react-router dev`
790
+
791
+ A hook or a route reads `ctx.server`, or the app declares a `page()`,
792
+ under `react-router dev` or `vite preview`; the same code works from
793
+ `bun build/server/index.js`.
794
+
795
+ **Why:** under `@alxia/react-router/vite`, Vite owns the dev server, and
796
+ the preview server under `vite preview`. An HTTP request reaches the app
797
+ through `app.fetch`, as in a test, not through `listen`: there is no
798
+ `Bun.serve` behind it, so `ctx.server` is `undefined`, the default
799
+ `ctx.ip` too, and a `page()` (Bun's HTML bundle) is not served. A socket's upgrade is the exception: the plugin relays it
800
+ to a `Bun.serve` of the app, so its hooks read a server
801
+ ([WebSockets](guide.md#under-react-router-dev)).
802
+
803
+ **Fix:** read `ctx.server` as optional, and publish to sockets from the
804
+ sockets themselves (`socket.publish`), which works in dev too:
805
+
806
+ ```ts
807
+ // app/server.ts
808
+ import { createServer } from '@alxia/react-router';
809
+
810
+ export default createServer({
811
+ configure: (app) =>
812
+ app.get('/api/uptime', ({ server, reply }) =>
813
+ reply.ok({ pending: server?.pendingRequests ?? null }),
814
+ ),
815
+ });
816
+ ```
817
+
818
+ Let Vite and React Router serve the HTML in dev; a `page()` is for an app
819
+ without React Router.
820
+
821
+ ### A `publish` under `react-router dev` misses the sockets opened before an edit
822
+
823
+ Two clients in one room; after an edit to `app/server.ts`, a message one
824
+ sends no longer reaches the other.
825
+
826
+ **Why:** an edit to the server makes a new app, and the plugin starts a
827
+ new `Bun.serve` for it on the next upgrade, so that new connections use
828
+ the new handlers. A socket opened before the edit stays on the previous
829
+ server, with the handlers it opened with, and a topic is one server's.
830
+
831
+ **Fix:** reconnect the clients after an edit: reload their pages. From
832
+ the build there is one server, and every socket shares its topics.