| 1 | --- |
| 2 | title: Dev servers |
| 3 | description: Run a framework dev server as a Command.Dev resource — started by alchemy dev, a no-op on alchemy deploy, with the server's URL surfaced back into your stack. |
| 4 | --- |
| 5 | |
| 6 | `Command.Dev` is a long-lived process scoped to the stack instance: |
| 7 | started during [`alchemy dev`](/cli/dev), restarted when its inputs |
| 8 | change, and a literal no-op during `alchemy deploy` — the deploy |
| 9 | provider returns `{ url: undefined }` without spawning anything. |
| 10 | This page assumes the [Command provider](/command) is registered. |
| 11 | |
| 12 | ## Declare a dev server |
| 13 | |
| 14 | Pass the command that starts your framework's dev server: |
| 15 | |
| 16 | ```typescript |
| 17 | import * as Command from "alchemy/Command"; |
| 18 | |
| 19 | const dev = yield* Command.Dev("Frontend", { |
| 20 | command: "npm run dev", |
| 21 | }); |
| 22 | yield* Console.log(dev.url); // e.g. "http://localhost:5173" |
| 23 | ``` |
| 24 | |
| 25 | Like every Command resource, `Dev` accepts `cwd` (for monorepo |
| 26 | packages), `env` (merged on top of `process.env`, with `Redacted` |
| 27 | values kept out of logs and state), and `shell` — without it the |
| 28 | command string is whitespace-split into binary and arguments, with |
| 29 | no quoting support. |
| 30 | |
| 31 | ## Where the process lives |
| 32 | |
| 33 | The child process runs inside the dev sidecar (see |
| 34 | `Command/Local.ts`) rather than in your Alchemy program's own |
| 35 | process, so it survives user-code HMR — Alchemy's user process can |
| 36 | restart without killing your `npm run dev` server. Its stdout and |
| 37 | stderr are mirrored to the terminal, preserving colored output. |
| 38 | |
| 39 | ## The url attribute |
| 40 | |
| 41 | `dev.url` is best-effort: Alchemy scans both stdout and stderr for |
| 42 | the first `http(s)://…` URL within a hard 5-second window, stripping |
| 43 | ANSI escape sequences first so Vite- and Next-style colored output |
| 44 | still matches. If nothing matches within the window, `url` is |
| 45 | `undefined` and the process keeps running. |
| 46 | |
| 47 | ## Lifecycle |
| 48 | |
| 49 | A `Dev` resource restarts its process when props change, keeps the |
| 50 | same PID across unchanged redeploys, and kills the process on |
| 51 | destroy. If the command exits within the 5-second startup window, |
| 52 | the deploy fails with a typed `CommandError` whose reason is |
| 53 | `UnexpectedExit` — match it with the exported guard, as the test |
| 54 | suite does: |
| 55 | |
| 56 | ```typescript |
| 57 | assert(Command.isCommandError(error)); |
| 58 | assert(error.reason._tag === "UnexpectedExit"); |
| 59 | expect(error.reason.exitCode).toBe(1); |
| 60 | ``` |
| 61 | |
| 62 | ## With StaticSite |
| 63 | |
| 64 | You rarely declare `Command.Dev` by hand for a frontend: |
| 65 | `StaticSite`'s `dev` prop declares one for you during `alchemy dev` |
| 66 | and skips the build entirely, letting your framework's own dev |
| 67 | server serve the site. See |
| 68 | [Static sites](/cloudflare/frontend/static-site) for the full story. |
| 69 | |
| 70 | ## Where next |
| 71 | |
| 72 | - [`alchemy dev`](/cli/dev) — the CLI mode that activates `Dev` |
| 73 | resources. |
| 74 | - [Memoized builds](/command/memoization) — `Command.Build` and |
| 75 | `Command.Exec`, the deploy-time counterparts. |
| 76 | |
| 77 | Reference: |
| 78 | |
| 79 | - [Dev API reference](/providers/command/dev) |