You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 2e8d867
Browse filesBrowse the repository at this point in the historyBrowse files
feat(platform): use Fetchable server entries for analog({ server }) (#2729)
The custom server entry is now a Fetchable module (https://fetchable.org):
its default export has fetch(request), and optionally close(), which the dev
server calls before reloading the entry. createApp() entries are no longer
supported. Node frameworks (Express, NestJS, Fastify) wrap their app with
srvx's toFetchHandler(). The NestJS template in create-analog and the custom
server and NestJS guides use the new entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3NKyLNGL2d5Df2wt4WNbA
Copy file name to clipboardExpand all lines: apps/docs-analog/src/content/features/server/custom-server.md
+76-48Lines changed: 76 additions & 48 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Custom Server
2
2
3
-
Serve an Analog app from your own server, such as NestJS, Express, Fastify or Hono, instead of Nitro. Your server owns the process and its routes, and Analog handles server-side rendering, page endpoints and server functions inside it.
3
+
Serve an Analog app from your own server entry instead of Nitro: a [Fetchable](https://fetchable.org) module that platforms and runtimes invoke directly, or a server built with a framework such as Hono, Express, NestJS or Fastify. Your entry owns its routes, and Analog handles server-side rendering, page endpoints and server functions inside it. A platform or runtime runs a Fetchable entry, while a Node server entry starts its own process.
4
4
5
5
:::info
6
6
@@ -55,49 +55,98 @@ Vite compiles the TypeScript in the entry's directory. Frameworks that use decor
55
55
56
56
## The server entry
57
57
58
-
The entry exports `createApp()`, which the dev server mounts behind Vite, and starts its own server in production. With Express:
58
+
The entry is a [Fetchable](https://fetchable.org) module: its default export has a `fetch(request)` method that returns a `Response`. `createAnalogRequestHandler()` handles Analog's requests and returns `null` for anything else:
- server functions (`/_analog/fn/:id`) and page endpoints (`/api/_analog/pages/**`);
79
+
- server-side rendering for `GET` requests outside `/api/`.
80
+
81
+
In development, Analog serves the entry's `fetch` behind Vite. In production, platforms and runtimes that invoke Fetchable modules, such as Netlify Functions, Deno and Bun, run the built entry without a server of your own.
82
+
83
+
### Fetch-native frameworks
84
+
85
+
Frameworks built on web `Request`/`Response`, such as Hono, are already Fetchable. Hand Analog the requests your routes don't handle, and export the app:
Passing the app's `fetch` lets [server-side fetches](#server-side-fetches) reach your Hono routes in-process.
107
+
108
+
### Node frameworks
109
+
110
+
Frameworks built on Node's request and response, such as Express, NestJS and Fastify, wrap their app with `toFetchHandler()` from [srvx](https://srvx.h3.dev). In development, the app gets the dev server's Node request and response directly. In production, the entry can start its own server. Install `srvx`, then with Express:
`createApp()` returns `{ handle, close? }`, where `handle` is a Node `(req, res, next)` handler. When server code changes in development, Analog creates a new app and calls `close()` on the previous one.
`createAnalogHandler()` handles Analog's own requests and passes everything else to `next()`:
139
+
`(req, res) => app(req, res)` calls Express without a `next` callback, so a request nothing handles gets Express's `404` instead of an empty response.
92
140
93
-
- server functions (`/_analog/fn/:id`) and page endpoints (`/api/_analog/pages/**`);
94
-
- server-side rendering for `GET` requests outside `/api/`.
141
+
When server code changes in development, Analog reloads the entry. Add a `close()` next to `fetch` to release what the previous entry holds, such as database connections; Analog calls it before using the new entry. See [NestJS](/docs/integrations/nestjs) for an entry that closes its app.
95
142
96
-
Register your own routes before it. Routes under `/api/` that nothing handles reach your server's 404 handling instead of server-side rendering.
143
+
`createAnalogHandler()` is the Node adapter for `createAnalogRequestHandler()`: it handles Analog's requests and passes everything else to `next()`. Register your own routes before it. Routes under `/api/` that nothing handles reach your server's 404 handling instead of server-side rendering. It also passes the Node request and response to Analog as the `REQUEST` and `RESPONSE` tokens; with `createAnalogRequestHandler()`, pass them yourself as `handleAnalogRequest(request, { req, res })`.
97
144
98
145
## Server-side fetches
99
146
100
-
Requests that server-side rendering and page endpoints make to the app's own origin, such as `HttpClient` calls to `/api/...` and the `fetch` passed to page endpoints, run in-process instead of over the network, as with Nitro. Analog serves its own routes. To serve your server's routes too, pass a `fetch` that handles a `Request`. It gets the requests Analog doesn't handle: routes under `/api/`, and non-`GET` requests elsewhere; other `GET` requests are server-rendered.
147
+
Requests that server-side rendering and page endpoints make to the app's own origin, such as `HttpClient` calls to `/api/...` and the `fetch` passed to page endpoints, run in-process instead of over the network, as with Nitro. Analog serves its own routes. To serve your server's routes too, pass a `fetch` that handles a `Request`, as the Hono example does. It gets the requests Analog doesn't handle, such as your own routes under `/api/` and non-`GET` requests outside `/api/`; other `GET` requests are server-rendered.
148
+
149
+
With a Node framework, forward these requests to the server's address:
101
150
102
151
```ts
103
152
// Your server's own address, never one taken from the request.
@@ -119,32 +168,11 @@ In development, set `SERVER_ORIGIN` to Vite's dev server origin, such as `http:/
119
168
120
169
## Static files
121
170
122
-
Serving client files is the server's job, with its own tools (`express.static`, `serve-static`, NestJS's `useStaticAssets()`, a CDN). In production they're in `dist/client`, next to the `dist/server` directory that holds the server entry:
171
+
Vite serves client files in development. In production they're in `dist/client`, next to the `dist/server` directory that holds the server entry. A Fetchable entry leaves them to the platform, usually its CDN. A Node server serves them with its own tools (`express.static`, `serve-static`, NestJS's `useStaticAssets()`). Either way:
123
172
124
-
-Register them **before** the Analog handler. Otherwise server-side rendering answers asset URLs.
173
+
-Serve them **before** the Analog handler. Otherwise server-side rendering answers asset URLs.
125
174
- Disable the index fallback (`index: false`), so `/` is server-rendered instead of returning the client's `index.html`.
126
175
127
-
## Fetch-native servers
128
-
129
-
Servers built on web `Request`/`Response`, such as Hono, use `createAnalogRequestHandler()`. It returns a function that takes a `Request` and returns a `Response`, or `null` for requests Analog doesn't handle:
Pass your app's `fetch`, `createAnalogRequestHandler({ fetch: (request) => app.fetch(request) })`, so [server-side fetches](#server-side-fetches) reach your Hono routes in-process.
145
-
146
-
Node servers that have the Node request and response can pass them as a second argument, `handleAnalogRequest(request, { req, res })`, so they become Analog's `REQUEST` and `RESPONSE` tokens. `createAnalogHandler()` does this for you.
147
-
148
176
## Streaming
149
177
150
178
[Streaming SSR](/docs/features/server/streaming-ssr) works with a custom server: enable `experimental.streaming` and export `renderStream` from `main.server.ts`. Analog returns the document as a stream with `Cache-Control: no-store, no-transform`, and stops rendering when the client disconnects.
@@ -173,7 +201,7 @@ Two details of locale detection apply here as on Nitro:
173
201
174
202
## Building and running
175
203
176
-
`vite build` builds the client into `dist/client` and the server entry into `dist/server/main.js`. Run it with Node:
204
+
`vite build` builds the client into `dist/client` and the server entry into `dist/server/main.js`. A Fetchable entry keeps its default export and doesn't listen on a port; deploy it to a platform or runtime that invokes Fetchable modules. Run a Node entry that starts its own server with Node:
@@ -104,52 +104,49 @@ Server-side rendering and page endpoints fetch Analog's own routes in-process. T
104
104
105
105
## Step 4: Create the server entry
106
106
107
-
The entry exports `createApp()`, which the dev server mounts behind Vite, and listens in production:
107
+
The entry is a [Fetchable](https://fetchable.org) module. srvx's `toFetchHandler()` turns NestJS's Express instance into the `fetch` the dev server calls, and the entry listens on its own port in production:
-`rawBody: true` keeps the original request bytes, so form actions and server functions can read bodies that NestJS's parsers already consumed.
150
146
- Static files are registered before `app.init()`. Anything registered after it sits behind NestJS's 404 handler, and `init()` is also where the Analog handler is mounted.
151
147
-`index: false` keeps `/` server-rendered instead of returning the client's `index.html`.
152
-
- In development, Analog creates a new NestJS app when server code changes, and calls `close()` on the previous one.
148
+
-`(req, res) => server(req, res)` calls Express without a `next` callback, so a request nothing handles gets a `404` instead of an empty response.
149
+
- In development, Analog reloads the entry when server code changes, and calls `close()` on the previous one.
The module from [Step 3](#step-3-mount-analog-in-your-module) stays the same. The entry creates a Fastify app and hands Vite Fastify's request handler:
181
+
The module from [Step 3](#step-3-mount-analog-in-your-module) stays the same. The entry creates a Fastify app and exports Fastify's request handler as its `fetch`:
@@ -239,10 +230,10 @@ How it differs from Express:
239
230
240
231
Under `vite`, NestJS runs inside the dev server rather than listening on its own port:
241
232
242
-
- The first request Vite doesn't serve itself calls `createApp()`, which runs `NestFactory.create()` and `init()`.
233
+
- The first request Vite doesn't serve itself loads the entry, which runs `NestFactory.create()` and `init()`.
243
234
- Vite serves client files and HMR, and passes every other request to NestJS, which routes it as in production.
244
235
- Vite compiles the NestJS code, so there's no `nest start --watch` or `tsc` step.
245
-
- When server code changes, the next request creates a new NestJS app and calls `close()` on the previous one, so shutdown hooks run.
236
+
- When server code changes, the next request reloads the entry, creating a new NestJS app, and calls `close()` on the previous one, so shutdown hooks run.
246
237
- To reach controllers from [server-side fetches](/docs/features/server/custom-server#server-side-fetches), point the origin at Vite's dev server, such as `http://localhost:5173`.
0 commit comments