@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.
- package/LICENSE +21 -0
- package/README.md +289 -2
- package/dist/assets.d.ts +12 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/chunks/index-kfekhvgr.js +31 -0
- package/dist/chunks/index-kfekhvgr.js.map +10 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +160 -0
- package/dist/cli/index.js.map +13 -0
- package/dist/cli/main.d.ts +9 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/reveal.d.ts +18 -0
- package/dist/cli/reveal.d.ts.map +1 -0
- package/dist/cli/template.d.ts +8 -0
- package/dist/cli/template.d.ts.map +1 -0
- package/dist/context.d.ts +70 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +13 -0
- package/dist/react-router.d.ts +61 -0
- package/dist/react-router.d.ts.map +1 -0
- package/dist/server.d.ts +99 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/vite/config.d.ts +33 -0
- package/dist/vite/config.d.ts.map +1 -0
- package/dist/vite/dev.d.ts +23 -0
- package/dist/vite/dev.d.ts.map +1 -0
- package/dist/vite/entry.d.ts +20 -0
- package/dist/vite/entry.d.ts.map +1 -0
- package/dist/vite/index.d.ts +33 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +391 -0
- package/dist/vite/index.js.map +16 -0
- package/dist/vite/node.d.ts +16 -0
- package/dist/vite/node.d.ts.map +1 -0
- package/dist/vite/preview.d.ts +8 -0
- package/dist/vite/preview.d.ts.map +1 -0
- package/dist/vite/runtime.d.ts +8 -0
- package/dist/vite/runtime.d.ts.map +1 -0
- package/dist/vite/socket.d.ts +68 -0
- package/dist/vite/socket.d.ts.map +1 -0
- package/docs/README.md +12 -0
- package/docs/guide.md +843 -0
- package/docs/roadmap.md +96 -0
- package/docs/troubleshooting.md +832 -0
- 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.
|