A Helix project is a set of TypeScript functions plus an optional React SPA, and the same code runs in two places. During development the CLI serves everything from one local server on localhost
. In production each project runs on a managed runtime behindhttps://{project-id}.helix-app.ai, and both runtimes call the same platform services for auth, scheduling, and the project key-value store.
Local development
helix dev starts a Nitro dev server on localhost
- Function routes go to the Nitro function runtime, which maps file paths in
functions/to URLs. - Static assets are served by Vite, integrated as a Nitro plugin.
- Everything else falls back to the SPA’s
index.html.
Because functions and the SPA share an origin, there is no CORS configuration in development. The deployed project keeps the same single-origin layout, so there is none in production either.
helix dev (localhost:3000)
|
|-- Function routes -> Nitro function runtime
|-- Static assets -> Vite (Nitro plugin)
|-- SPA fallback -> index.html
|
|-- Local KV, in memory <- ctx.kv() (always local)
|
+-- Remote auth service <- ctx.http.authed() (always remote)
The one exception is authentication. ctx.http.authed('alias') always resolves against the remote Helix auth service, even during local development, because credentials are never stored on your machine. Schedules in functions/_scheduled/ fire in production only; helix dev warns if you hit those routes locally.
Production
helix deploy builds the project and ships it to the platform. The build bundles your functions into a deployable package that routes each request to the correct handler, compiles the SPA with vite build if app/ exists, and generates a manifest describing routes, schedules, and schemas. The deploy then:
- Uploads the function bundle to the project’s managed runtime.
- Uploads SPA assets for CDN delivery.
- Registers triggers: the platform reconciles the schedules extracted from the build.
- Updates routing and switches traffic to the new version.
All production traffic arrives at the project’s subdomain and routes by match priority:
{project-id}.helix-app.ai
|
CDN edge
|
|-- 1. Function route match -> project runtime
|-- 2. Static asset match -> CDN cache
+-- 3. SPA fallback -> index.html
Nitro exists only in development; production uses the managed Helix runtime. Functions scale with demand, scale to zero when idle, and each project’s concurrency cap keeps one project from consuming unbounded resources. See the deployment guide.
Platform services
Services that both runtimes call:
| Service | What it does |
|---|---|
| Credential resolution | Resolves auth aliases to credential records, injects credentials server-side, and logs every authenticated call. See the security model. |
| Schedules | Holds each project’s schedules and fires functions/_scheduled/ functions in production. helix dev does not run schedules locally; see scheduled functions. |
| Key-value store | Project-scoped ctx.kv() storage. Local runs use an in-memory store; production uses the managed store. See the key-value store guide. |
There is no managed database in the shipped product today. Use ctx.kv() for project state, or reach an external database through an authenticated HTTP API.
The life of a request
Here is what happens when a browser calls GET https://{project-id}.helix-app.ai/api/v1/orders on a deployed project:
- The CDN edge receives the request and checks it against the deployed route table.
/api/v1/ordersmatches a function route. - The request reaches the project’s runtime, which routes to the handler compiled from
functions/api/v1/orders.get.ts. - The runtime validates the merged input (body, params, and query) against the function’s exported Zod schema, then calls the handler with
ctx. - Inside the handler,
ctx.kv()reads or writes project state, and anyctx.http.authed('alias')call has its credentials injected by the platform before the request is forwarded. - The handler’s return value becomes the response and travels back through the edge to the browser.
- Every step is recorded under one execution ID, which you can trace in the dashboard at
https://app.helix.tray.ai.
A request for a static asset such as /assets/main-abc123.js never reaches the function runtime: the CDN serves it from cache at step one.