Targ Apps Docs
Chroma

Server

What the chroma dev server serves, on which ports, with which MIME types and status codes, and how to deploy dist/ to a static host.

chroma dev serves your project with a small local HTTP server, the engine binary built from Chroma-Server. It only sends files: index.html, JavaScript, .chr modules, CSS, and chroma_bg.wasm. It does not run your app logic or API route handlers; those run in the browser after the files load.

The dev server is the usual Portable (Web) entry during development (chroma dev --no-desktop). Executable (Chroma Desktop) develop mode still uses this server in the background; Desktop only provides the window and host bridges (Runtime modes).

Who serves what

SituationServerPortBind address
chroma devInstalled engine binarydev.port from chroma.json, --port, else 4173127.0.0.1
engine run by handSame binary--port, else 4173127.0.0.1
Chroma Desktop library (imported apps)Server built into Desktop, a separate implementation of the same design47823, or a free port if that one is busy127.0.0.1
ProductionYour static host, not engineHost-definedUsually public

Ports

  • The dev server and Desktop's built-in server are different programs on different ports. chroma dev with dev.desktop: true starts the dev server on the dev port and opens Desktop pointed at http://127.0.0.1:<dev port>/<entry>. Apps you import into Desktop run from port 47823.
  • Desktop keeps 47823 fixed so the origin, and therefore IndexedDB data, survives restarts. If another program holds the port, Desktop falls back to a random free port and storage will not persist across restarts.
  • The dev server binds the loopback interface only. There is no option to listen on 0.0.0.0, so other machines on your network cannot reach it. If the port is taken, the server logs an error and exits with status 1.

Contract

TopicBehavior
MethodsGET and HEAD only. Anything else returns 405. This includes a POST to /api/...: API routes run in the browser through @chroma/router, not here.
TLSNone.
Directory listingNone. / and any directory path serve that folder's index.html, else 404.
Query string and fragmentStripped before the file lookup, so /app.js?v=1 serves /app.js.
SPA fallbackNone. Unknown paths return 404; they are not rewritten to index.html.
Percent-encodingPaths are decoded (%20 becomes a space) before lookup.
ConnectionsOne thread per connection, no keep-alive. Every response sends Connection: close.
Request sizeOne 8 KB read. Request bodies are never read, and headers beyond 8 KB are cut off.
Headers sentOnly Content-Type, Content-Length, and Connection. No caching, CORS, Date, or Server headers.

Status codes

StatusWhen
200The file was found and read. HEAD returns the same headers with no body.
403The resolved path (after .. segments, encoded traversal, and symlinks) is outside the served folder.
404The path does not exist, or a directory has no index.html.
405The method is not GET or HEAD.

Error bodies are plain text (Not Found, Forbidden, Method Not Allowed). Path safety does not rely on string checks: the server resolves the real path of every request and verifies it still lies inside the root.

MIME types

The type comes from the file extension only:

ExtensionContent-Type
htmltext/html; charset=utf-8
js, mjs, chrtext/javascript; charset=utf-8
csstext/css; charset=utf-8
json, mapapplication/json; charset=utf-8
wasmapplication/wasm
svgimage/svg+xml
pngimage/png
jpg, jpegimage/jpeg
icoimage/x-icon
txttext/plain; charset=utf-8
anything elseapplication/octet-stream

Run it by hand

You rarely need to, but you can serve any folder, for example to smoke-test a build:

cd my-app
chroma build
engine --port 4173 --dir ./dist   # open http://127.0.0.1:4173/
FlagDefaultMeaning
--port <n>4173Port to listen on.
--dir <path>.Folder to serve. If it does not exist, the server exits with status 1.
--reload, --no-reloadreload offLive reload switches; see Live reload.

CHROMA_LOG_LEVEL filters the server's log output; the values are in the CLI log levels table.

Dependencies and live reload

Static serving uses only the Rust standard library. The live-reload feature adds the sha1 crate for the WebSocket handshake. It watches files by polling modification times every 200 ms, and it injects /__chroma/dev-reload.js into served HTML only while reload is enabled. The crate manifest also lists notify, which the current source does not use. The endpoints, watched files, and every switch are documented in Live reload.

Deploying dist/

After chroma build, upload the contents of outDir (default dist/) to any static host. You do not deploy engine.

AssetRequirement
index.htmlThe entry point, served at /.
bundle.js (dynamic) or minified scripts (static)Served as JavaScript.
chroma_bg.wasm (dynamic)Served as application/wasm, in the same folder as the bundle.
plugins/ and chroma.jsonFetched at runtime by plugins/loader.js; keep them.

Mirror the table above on your host: .wasm as application/wasm and .js/.mjs/.chr as JavaScript. Browsers cannot load .wasm from file://, so always use HTTP or HTTPS.

Plugins in dist/plugins/ load the same way on a static host as in chroma dev, but side effects (IndexedDB partitions, Chroma-Fs backend choice, live reload) depend on the runtime mode. The API router still runs only in the page; this server never executes api/ handlers.

For the Executable path, upload is not required: users import the zip from chroma build into Chroma Desktop, which serves the same files from loopback HTTP on a different port (Who serves what).

Next

On this page