---
title: Deployment
description: Four targets behind one interface, how the build produces them, and the known limitations.
---

`rshono build` targets one platform. Pick it with `deploy` in the config, `--deploy <name>` or
`RSHONO_DEPLOY`, in that precedence order. The default is `node`. `rshono dev` always runs the Node dev
server whatever you choose.

## The targets

| `deploy`     | Handoff                          | Assets & prerendered pages                                      | After `build`                               |
| ------------ | -------------------------------- | ---------------------------------------------------------------- | ------------------------------------------- |
| `node`       | binds a port                     | from `dist/` on disk                                             | `rshono start`                              |
| `cloudflare` | `{ fetch }` default export       | Workers Assets; prerendered pages read via the `ASSETS` binding  | `wrangler deploy`                           |
| `vercel`     | web handler in a Node function   | CDN for assets; prerendered pages inside the function            | `vercel deploy --prebuilt`                  |
| `aws-lambda` | streaming handler (Function URL) | from the deployment package                                      | zip `dist/`, handler `dist/server/main.mjs` |

**Every target streams** — a page's HTML reaches the browser as it renders. That is the bar a new target
has to clear. Bun and Deno run the `node` build through their `node:` compatibility
(`bun dist/server/main.mjs`), so neither needs a preset of its own.

`rshono start` refuses a build made for another platform rather than starting a bundle with no listener
in it.

## Cloudflare

A Worker resolves no `node_modules` at runtime, so the build bundles **all** dependencies; one that
needs a real `node:` API beyond `nodejs_compat` will not work. The build scaffolds a `wrangler.jsonc` if
the project has none — including `nodejs_compat`, which the request context needs for
`AsyncLocalStorage` — and never touches it again.

Bindings (D1, KV, R2) arrive as `getRequestContext().env`. They are not available under `rshono dev`,
which is plain Node.

## AWS

A Lambda Function URL with the invoke mode set to `RESPONSE_STREAM`, usually with CloudFront in front for
`/_static` and `public/`. **Lambda@Edge is deliberately not a target**: CloudFront returns the response
as a value rather than a stream, caps a generated origin-request response near 1 MB, and supports no
environment variables, so `getRequestContext().env` would be empty there.

## Prerendered pages are never CDN-served

One URL answers with an HTML document or a flight payload depending on `Accept`, and a path-keyed CDN
cannot choose. The app always handles page URLs. Assets under `/_static` and `public/` do go straight to
the CDN where there is one.

## How the build works

Two coordinated Rspack compilers, using native RSC support (`rspack.experiments.rsc`):

- **client** (`target: web`) → `dist/static`: hydration runtime, `'use client'` chunks, CSS.
- **server** (`target: node`) → `dist/server/main.mjs`: a Hono app assembled from your routes, rendered
  through two layers — the RSC layer, with the `react-server` condition, produces the flight payload;
  the SSR layer turns it into an HTML stream with the payload inlined for hydration.

Everything in that bundle that depends on *where* it runs — binding a port, serving `/_static` and
`public/`, reading a prerendered page, loading `.env` — sits behind a single interface the build resolves
per target. The request-handling code has no platform in it.

In development the CLI watches both bundles and runs the server bundle **in a worker thread**, restarted
per rebuild, with requests gated on readiness so nothing drops across a restart. Client edits hot-apply
via react-refresh; server component edits re-fetch the payload in place. Browser state survives both.

In production `dist/server/main.mjs` is self-contained — React, Hono and the framework are bundled in;
your other dependencies resolve from `node_modules`.

## Limitations

- **No compression.** It belongs in a proxy, a load balancer or a CDN, and every hosted target already
  does it. `hono/compress` in `src/server.ts` if you need it in-process — read its docs on streaming
  first, because a buffering compressor undoes streamed SSR.
- **No prefetching.** A navigation fetches when it is asked for; there is no speculative warming.
- **Scroll restoration is the browser's.** `history.scrollRestoration` stays `auto`, so a traversal's
  offset is the browser's to restore. A new page starts at the top, or at the `#hash` the link named.
- **No base path.** `siteUrl` must be a bare origin.
- **Wildcard, optional and regex params cannot be prerendered.**
- **The dev-mode proxy does not forward WebSocket upgrades** to a custom sub-app. Production is
  unaffected.
- **Dev source maps embed the original source of `'use server'` modules.** Dev binds to 127.0.0.1 only,
  and production ships no client source maps.
