> ## Documentation Index
> Fetch the complete documentation index at: https://docs.revyl.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Web builds

> Build a Web bundle with Revyl, or register a preview deployment you already host as a build.

<Note>
  Web is in alpha. To add it to your organization, email [support@revyl.ai](mailto:support@revyl.ai).
</Note>

A Web build is either a bundle built by Revyl or a registered deployment URL. Managed builds run
your recipe locally or on Revyl's build runners, then upload a verified bundle. URL builds open
an existing preview deployment. You choose and configure your deployment host; Revyl doesn't
generate a host-specific CI workflow.

For Next.js and static Vite recipes and test-only sign-in, start with the
[Web framework guide](/frameworks/web).

Prompt for your coding agent:

```text
Set up Web builds for this app in Revyl.

Read https://docs.revyl.com/builds/web.md and follow it. It's the current Revyl guide for this, so prefer it over anything you remember about Revyl.

Web is in alpha: if Revyl says Web isn't enabled for my organization, stop and tell me to email support@revyl.ai. If the app already deploys previews, register each preview URL with `revyl build upload --platform web --url <url> --app <web-app-id>`; otherwise add a Web recipe so `revyl build` builds the bundle.

You're done when a Web build of the app appears in Revyl and opens in a Web session.

Ask me before anything that needs my login or approval.
```

## Create a Web app

In the dashboard, click **Create app**, pick **Web**, and optionally enter your first deployed
HTTPS URL and commit SHA. Web apps don't need a package or bundle ID. To add a URL later, click
**Upload build** on the **Builds** page, pick the Web app, and enter the HTTPS URL.

From the CLI:

```bash
revyl init -y
revyl app create --name "My Web App" --platform web
```

`revyl init` doesn't detect Web frameworks, package managers, or preview hosts, create a Web
app, or write a Web recipe. Add the app to `.revyl/config.yaml` yourself:

```yaml
build:
  profiles:
    preview:
      web:
        app_id: <web-app-id>
```

This app-ID-only profile supports URL registration and session start. A Web-only build needs no
`build.framework`, and a Web recipe can sit beside `ios` and `android` recipes.

## Build a Web bundle

Add a recipe with your project's package manager and build command:

```yaml
build:
  profiles:
    preview:
      web:
        app_id: <web-app-id>
        setup_commands: [npm ci]
        build_commands: [npm run build]
        output_path: dist
```

```bash
revyl build --profile preview --platform web
revyl build --profile preview --platform web --local
```

The default runs remotely; `--remote` is also accepted. `--local` uses your installed
toolchain and still uploads the result. Both paths use the same recipe, app association, and
build lifecycle. Remote builds require a Git commit and record the original HEAD even when the
source includes local edits.

`output_path` must be one dedicated project-relative output directory, without globs. Only its
contents enter the `.tar.zst` bundle, not the source checkout. Symlinks, special files, `.git`,
`.revyl`, `.env*`, and credential files are rejected. Compressed and unpacked data are each
limited to 200 MiB, with at most 20,000 archive entries and 1,024-byte paths. Static output
needs an `index.html` at its root.

`revyl build` requires explicit `build_commands` and `output_path`; it never guesses a build
command or turns an app-only profile into a recipe. Building a URL-only profile fails with
`profile <name> is URL-only; add build_commands and output_path, or register previews with revyl build upload --url`.

### Server-rendered apps

Package the server and all of its runtime dependencies inside the output directory, and give a
start command relative to the bundle root:

```yaml
output_path: dist-server
run:
  start: node server.js
  port: 3000
  healthcheck: /health
  start_timeout_seconds: 60
  env_keys: [APP_URL]
  secret_names: [API_TOKEN]
```

The server must listen on all interfaces (`0.0.0.0`) at the configured port, not only
`localhost` or `127.0.0.1`; the browser reaches it at `http://revyl-app.localhost:<port>`. Revyl sets
`HOSTNAME=0.0.0.0` and `PORT` for the process. The app is ready when `GET <healthcheck>`
returns any 2xx or 3xx status; redirects aren't followed. A localhost-only app fails the health
check with `couldn't connect to revyl-app:<port>`. The defaults are port `3000`, healthcheck
`/`, and a 60-second startup timeout (10 to 600 seconds allowed).

Without `run.start`, the session serves static output. A request for a missing path without a
file extension gets `<path>.html` when that file exists, otherwise the root `index.html`, so
client-side routes and clean URLs load on reload or deep link. A missing asset such as
`/app.js` still returns 404.

`run.env_keys` names existing plaintext organization launch variables, and `run.secret_names`
names encrypted ones. These are names, never values or `KEY=VALUE` assignments. They're
injected only into the app process when the session starts it, not into the browser or the
build. Build-time settings stay in `env` and `secrets`, as for mobile recipes. Never bake
secrets into the output directory.

## Register a preview from CI

```bash
revyl build upload --platform web --url https://pr-42.example.dev --json
```

- **App.** `--app <name-or-id>` selects the Web app. Without it, the command reads
  `build.profiles.<name>.web.app_id`. Profiles that name different Web apps need `--app`.
- **URL.** Must be HTTPS, with no credentials, query string, or fragment. Revyl doesn't fetch
  anything when it registers the URL.
- **Pull request.** The command stores the same CI metadata on the build as mobile uploads do.
  GitHub Actions and Buildkite are detected automatically. On any other runner, pass
  `--repo owner/name --commit <full PR head SHA>`, and add `--pr <number>` to choose the pull
  request explicitly. `--commit` requires `--repo`.
- **Branch.** `--branch <name>` registers the build on that Revyl branch, creating it if needed;
  without it, the build lands on the app's default branch. Give each pull request its own branch
  (for example `--branch pr-42`) so previews never become what the default branch opens.
- **Latest.** Registering the same URL and commit again returns the existing build
  (`"was_reused": true`) and makes it its branch's latest.

`--file`, `--header`, `--version`, `--pin`, `--set-current`, `--no-set-current`,
`--preview`, and `--name` are rejected by `build upload --platform web`.

`pr_review.build.profile` can name a profile with a complete Web build recipe. Naming a URL-only
Web profile there is rejected with the same URL-only error.

## Open a build

```bash
revyl device start --platform web
```

This opens the newest Web build on the app's default branch, using the app from config.
`--branch <name>` opens the newest build on that branch, `--all-branches` the newest across
every branch, and `--build-version-id` a specific build. `--url` registers a URL (on
`--branch` when given) and opens it in one step. `--build-selection inherit` and `pinned`
aren't available for Web. See [Sessions](/sessions#web-sessions) to start one from the
dashboard.

### When a bundle's app fails

If a bundle's app can't start, or exits during the session, the session fails with a specific
reason, for example "Your app exited before it was ready (exit code 1)", a health check that
didn't return 2xx or 3xx in time, a bundle that didn't match the build's size or checksum, or a
missing launch variable named in `run.env_keys` or `run.secret_names`. The CLI, MCP tools, and
the session's failure state in the app all show the reason.

Revyl keeps the last 64 KiB of the app's combined stdout and stderr. Members of the organization
see the last lines in the live session's failure state and in the session report, and can
download the whole log from either. Public report links show only the reason. Values of the
variables injected through `run.env_keys` and `run.secret_names`, and token-shaped strings
such as JWTs and signed-URL signatures, are redacted before the output leaves the session.
