Skip to main content
Each App’s sandbox runs a dev server: celld dev, from celld, the Judt runtime, on port 3000. The dev server runs the same Worker code that production runs, with its own data. Use it to test each change before you deploy.

Before you begin

To follow these steps, you need a way to run commands in the App’s sandbox, such as the in-product agent, the API, or the MCP server. The sandbox starts when a request needs it. The platform starts the dev server when the sandbox starts. The first start runs npm ci and the build, so it can take some seconds. If the dev server stops, for example after a crash or a failed npm ci or build script, then nothing starts it again until the next preview request. Until then, curl to port 3000 gets “connection refused”. If the start failed, then the preview request reports the failure and doesn’t start the dev server again until a file changes. To send a preview request, use the in-product agent’s preview tool, the MCP server’s create_preview tool, or POST /api/apps/{id}/preview. For more information about sandboxes and preview URLs, see Sandbox.

How the dev server starts

When the dev server starts, it prepares the App the way that a deploy does, and then it serves the App. It does the following, in order:
  1. If the package-lock.json file differs from the one that the last install used, then it runs npm ci. Install scripts don’t run.
  2. If the package.json file has a build script, then it runs the script one time.
  3. If the package.json file has a dev script, then it starts the script and keeps it running beside the dev server. Use the dev script for a watcher that rebuilds on change, such as tsc --watch.
  4. It starts celld dev on port 3000.
The sandbox doesn’t keep node_modules when it stops, so the dev server installs the dependencies again after the sandbox starts. A change to the package.json file takes effect the next time that the dev server starts. You don’t need to restart anything yourself. For more information about the build and dev scripts, see npm packages and builds.

Test a change

1

Check the syntax

In the sandbox, check each module that you changed:
If the App has a build script, run the build instead. node --check checks only the syntax. Node.js can’t run Worker code, so a module that passes the check can still fail on the dev server.
2

Send a preview request

Send a preview request before you send other requests. It starts the dev server if it isn’t running, and it waits for the rebuild that your change started. If the build or the start failed, it returns 422 preview_failed with the build’s output. Without this step, curl can get an answer from the previous build.
3

Send a request

In the sandbox, request a route from the dev server:
To open the App in a browser, use the URL that the preview request returned.
4

Read the logs

If a request fails, read the App’s logs. The dev server’s entries have the development environment. For more information, see Troubleshoot.

Logs and failed builds

The App’s logs collect the following entries from the dev server:
  • Your code’s output: each console.* call and each uncaught exception, with the app source. Production writes the same entries.
  • The dev server’s own events: builds, reloads, and exits, with the dev_server source. A dev.build_failed entry includes the bundler’s report.
If a build fails, then the previous build keeps serving, so a request can still get an answer. A preview request returns 422 preview_failed until the App builds again. You can also check the logs for a dev.build_failed entry.

Dev data

The dev server keeps its data in the .celld/dev/ directory. The data stays after the dev server restarts. Dev data and production data are separate. Commands can’t read the .celld/dev/ directory. To read dev data, send a request to the App.

Generated dev server files

The dev server runs from .judt-dev.json, a copy of the wrangler.json file that the platform keeps. For an App with D1, its main key names .judt-dev.js, which loads your entry module and applies the migrations. Don’t edit or delete these files. If a dev server error names .judt-dev.json, then correct your wrangler.json file.

Test D1 migrations

The dev server applies D1 migrations to the dev database, as a deploy does to production. Each time a build starts to serve, the dev server applies the pending files in the migrations/ directory. A new file applies after the rebuild that it starts. Don’t write code that applies migrations. If a migration fails, then the preview request fails with preview_failed and the SQL error, and nothing from that file stays. Correct the file, and the next rebuild applies it. A file that the dev server applied doesn’t apply again when you change it, so add a new file. For more information, see D1.

Test a Cron Trigger

The dev server runs Cron Triggers on their schedule, and you can’t start one manually. To test the job, put it in a function, and call the function from a temporary route. Remove the route after the test:
index.js
In this example, cleanup is the job function, and json is a helper that returns a JSON response. For more information, see Cron Triggers.

Secrets on the dev server

Your code reads the App’s Secrets as env.NAME on the dev server, as it does in production. For more information, see Vars and Secrets.