---
title: "Where it runs"
description: "A host is a Nitro preset, not a server you write."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.rsc-kit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Where it runs

There used to be a page here for each runtime, and a server file generated to
match. There is no server file now, and no choice about it either:
[Nitro](https://nitro.build) builds the server around the route tree, and
choosing where an app runs is choosing a preset.

```ts title="vite.config.ts"
import { defineConfig } from 'vite';
import { nitro } from 'nitro/vite';
import react from '@vitejs/plugin-react';
import { rscKit } from '@rsc-kit/core/vite';

export default defineConfig({
  plugins: [
nitro({ preset: 'bun', serveStatic: 'inline' }),
rscKit(),
react(),
  ],
});
```

`bun create rsc-kit@latest my-app` writes that for you. Changing where it deploys is
changing the one string.

## The presets

`rsc-kit` asks about three, because those are the ones it can check for you.
Nitro carries [many more](https://nitro.build/deploy) and they need nothing from
this package — a preset is a string, not an integration.

| `--host` | preset | what you get |
| --- | --- | --- |
| `bun` | `bun` | `.output/server/index.mjs`, and `bun run compile` for a single binary |
| `node` | `node` | `.output/server/index.mjs`, run with `node` |
| `worker` | `cloudflare_module` | a Worker, plus the `wrangler.json` and `_headers` Nitro generates |

Everything else — Vercel, Netlify, Azure, Deno, AWS Amplify — is the same
change:

```ts
nitro({ preset: 'vercel', serveStatic: 'inline' })
```

## Running it

```bash
npm run dev      # vite is the renderer; nothing is prebuilt
npm run build    # writes .output/
npm run start    # runs .output/server/index.mjs
```

A Worker has no `start`, because it is deployed rather than started:

```bash
npm run preview  # wrangler dev, on workerd
npm run deploy   # nitro deploy --prebuilt
```

## Compiling to a single binary

Bun only, and the whole application ends up inside one file — engine, route
tree and assets:

```bash
npm run compile   # builds, then bun build --compile
./dist/app
```

It builds first on purpose. Compiling whatever `.output` happens to hold means
a binary one version behind the source with nothing to say so — and on a
project that has never been built, an `ENOENT` naming a path the app did not
write.

> **A Worker serves the frozen pages from a module**
>
> A Worker has no filesystem, so the directory the build writes is not there
> at request time. The build also writes the same pages as one module beside
> the bundle, `rsc-static-inline.mjs`, which wrangler uploads with the rest
> and the server imports when the directory is missing. Nothing to configure;
> it is why `○` on a Worker means the stored page and not a live render that
> happens to be the same. It counts toward the Worker's script size, which is
> worth knowing on a site with hundreds of frozen pages.

> **Frozen pages stay outside the binary**
>
> The build freezes pages into `.output/server/rsc-static` and the server reads
> them from there. A compiled binary has no filesystem to read — the directory
> is not embedded — so it renders those pages live instead. Everything still
> answers; what you lose is the stored render, not the page.

> **serveStatic: 'inline' is what makes this work**
>
> Without it the binary compiles, starts, serves pages, and 404s every asset.
> Inside a compiled binary the static path resolves into Bun's virtual
> filesystem — where the files on disk are not — and the failure is
> `ENOENT: /$bunfs/public/assets/…` with the pages themselves looking fine.
>
> The generated config sets it. If you write your own, set it too.

## Offline

`rscKit({ offline: true })` writes a service worker into `.output/public`
alongside the assets, so a page someone has visited survives a reload with no
network. Off by default, and covered in [Offline](/guides/offline#surviving-without-one).

## Why `nitro` is pinned

The generated `package.json` pins an exact version rather than a range:

```json
"nitro": "3.0.260903-beta"
```

Nitro's own `latest` tag is a dated prerelease, and it sorts **above** the plain
`3.0.0` on npm. So `^3.0.0` resolves to the older release, which builds without
complaint and then answers 404 to every route. TanStack Start pins a dated beta
for the same reason.

## Assets are Nitro's

The build writes browser assets to `.output/public`, and Nitro serves them from
its own root. There is no `assetsDir` or `assetsUrl` to set: both were removed,
and `rscKit()` refuses a config that still passes them rather than reading a
prefix and ignoring it.

The alternative was the silent version — the markup asks for the app's prefix,
Nitro answers at its own, and the page arrives unstyled and never hydrates with
nothing logged anywhere.

Assets live in `.output/public` and are served by the same process that serves
your pages. If you want nginx or a CDN serving them instead, point it at
`.output/public` — that directory is the deployment.

---

Next: [Deploying →](/hosts/deployment)

Source: https://docs.rsc-kit.dev/hosts/where-it-runs/index.mdx
