Workers
Eurobase builds supported JavaScript frameworks with pinned framework adapters and Wrangler, and runs the result on workerd, the open-source Workers runtime, on Eurobase servers. Static assets and the Worker are served from your project's one address.
Availability
Workers is available to every project. On its first deployment, a project that uses one of the frameworks below runs as a Worker. A project that already runs as a container or a static site keeps its runtime; see Containers and static sites. If Eurobase pauses Workers, projects that already run on it keep serving their current deployment, and new Workers builds are refused until it resumes.
Supported frameworks
| Framework | Package and minimum version | Build adapter |
|---|---|---|
| Next.js | next 14.2.35 | @opennextjs/cloudflare (OpenNext) |
| Astro | astro 5.7.0 | @astrojs/cloudflare |
| SvelteKit | @sveltejs/kit 2.20.3 | @sveltejs/adapter-cloudflare |
| Nuxt | nuxt 3.21.0 | nitro-cloudflare-dev |
| React Router | react-router 7.0.0, with vite 6.1.0 | @cloudflare/vite-plugin |
| Vite (React and other Vite apps) | vite 6.1.0 | @cloudflare/vite-plugin |
| Hono | hono 4.0.0 | None. Needs a wrangler.json, wrangler.jsonc or wrangler.toml |
The version is the one your lockfile resolves. Eurobase never upgrades your framework: an older version stops the deployment and says which version you need. When your repository has no Wrangler config, the build runs framework setup, which adds the framework's adapter at a version Eurobase pins, with a warning when that version isn't reviewed for your framework version. Your repository isn't changed. A deployment can carry warnings, for example for a framework version that is past its end of life or has known vulnerabilities.
How the runtime is chosen
Eurobase picks the runtime from your repository on the project's first deployment and keeps it. A project goes to Workers when its package.json declares one of the frameworks above and it isn't a Node server. A Node server is a project that declares express, fastify, @nestjs/core, koa, @hapi/hapi, restify, @adonisjs/core or @feathersjs/feathers, or whose start command runs its own file, such as node server.js. A framework's own server start, such as next start or SvelteKit's node build, doesn't count.
The build then checks the repository for a supported Worker build. These projects build as containers instead:
- a framework that isn't in the table;
- more than one framework in the same root directory;
- a workspace (monorepo) root; set the project's root directory to the app instead;
- a Wrangler config with
pages_build_output_dir; - Hono without a Wrangler config;
- Node servers, and every project that isn't a Node project, including plain HTML sites without a
package.json.
Later commits don't move a project to another runtime. If a commit would, the deployments page asks you to confirm with Switch runtime and deploy.
Build
The build installs exactly what your lockfile lists. Without a Wrangler config it runs framework setup and that setup's build command; with one, it runs build.command from your config, or else the framework's default. It then packages the Worker with wrangler deploy --dry-run. The project's Build and Start command settings aren't used for Workers. A build gets 4 vCPU, 6 GB of memory and 45 minutes; the source can be up to 512 MiB (1 GiB unpacked) and the build output up to 512 MiB.
Commit exactly one lockfile: package-lock.json or npm-shrinkwrap.json (lockfile version 2 or 3), pnpm-lock.yaml, yarn.lock (Yarn classic or Berry) or bun.lock. The package manager version comes from a committed Yarn release (yarnPath), else an exact packageManager field in package.json, else the default for the lockfile: npm 11.17.0, pnpm 11.27.0, Yarn 1.22.22 or 4.18.0. Bun always runs 1.4.2. A pnpm lockfile older than 9.0 needs a packageManager field.
A release must stay within these limits, or the build fails with the reason:
- Worker bundle: 10 MiB gzip-compressed and 25 MiB uncompressed, at most 256 modules.
- Static assets: at most 20,000 files of up to 25 MiB each.
_headersand_redirects: up to 1 MiB each, 100 header rules, and 2,000 static plus 100 dynamic redirects.compatibility_dateno later than the date Eurobase's workerd version supports, and noexperimentalorunsafecompatibility flags.
Before activation, Eurobase renders up to 16 concrete pages from the build output, including / and generated dynamic URLs when available. Next.js API routes and unresolved parameters are excluded. Checks have 45 seconds total, with up to 10 seconds per response and 2 MiB per HTML/React render. A 5xx, rendering error or incomplete render stops activation and shows the route and reason. The previous release keeps serving. A 401 login page or an API-only Worker's 404 is accepted. This is a bounded smoke check, not a check of every app state.
When a commit can’t deploy
For a Workers project, these stop the deployment with an explanation and the fix, and the current deployment keeps serving:
- a framework or Vite version below the minimum, or one the lockfile doesn't resolve (Vite 6.0 needs 6.1.0 or later);
- no lockfile, or more than one;
bun.lockb: runbun install --save-text-lockfileand commitbun.lock;- an npm lockfile version 1: run
npm install --package-lock-only; - a
packageManagerfield that doesn't match the lockfile or isn't an exact version.
Variables and bindings
The project secrets bound to the app are available when the Worker runs, in env and, with Node.js compatibility on, in process.env, next to the vars from your Wrangler config. A secret must not share a name with a configured binding, including a var: a cold start is refused with 503 secret-binding-conflict. Rename the secret or binding before redeploying. Today secrets aren't available during the build. Saving a secret starts a new deployment of a live Worker using the existing release. The secret change stays saved even if activation fails; cold starts using the previous secret configuration can be refused until a new deployment succeeds.
The only bindings available are ASSETS and a service binding to the Worker itself, which OpenNext uses. A Wrangler config that declares KV, D1, R2, Durable Objects, queues or other services fails the build with unsupported_binding. Bindings a framework adapter adds on its own, such as Astro's sessions and images, exist but calls to them fail with 501. The Cache API accepts calls but stores nothing, so every lookup misses.
Runtime limits
A Free Worker instance has a 160 MiB memory limit and, after startup, at most half of one vCPU. Starter and Pro instances have a 256 MiB memory limit and at most one vCPU after startup. The resource class is selected from the organization's plan when a deployment's route is staged and stays with that route. Changing plan does not resize an existing route or its running instance; a new deployment selects the current plan's class.
The following limits apply to both resource classes.
| Limit | Value |
|---|---|
| Memory recovery | An instance whose memory keeps growing can be replaced before it reaches its memory limit; one that exceeds it is stopped. |
| Startup | A cold start must be ready within 5 s and may use up to 2 s of CPU. A Worker that keeps failing to start is retried after a pause that grows from 2 s to 60 s. |
| Response time | The response must start within about 50 s of the request, including any cold start or wait for a slot: 504 deadline-exceeded, or 503 startup-timeout when the Worker never started. |
| Busy CPU | An instance that computes for 30 s without sending anything back is stopped (502). |
| waitUntil | Work passed to ctx.waitUntil is protected for 30 s after the last request ends. After that an instance that keeps its CPU busy is stopped, and an idle one can be stopped at any time. |
| Requests in flight | 128 per host, with no fixed limit per project or organization. A project alone on a host can use 112 of them. The last 16 are kept for organizations that become busy while others fill the host: each can take 4 of them at once, without waiting for slow requests to finish. When organizations compete, each gets an even share, split evenly among its busy projects. A request that cannot start waits up to 100 ms in a queue of 64. Freed slots go first to projects below their share. After the wait the request gets 429 (its project or organization is at its share) or 503 admission-full (the host is full), with Retry-After: 5. |
| WebSockets and streams | 512 per host, shared the same way: up to 448 for a project alone, and 16 of the last 64 for each organization that becomes busy. A stream that cannot open is refused at once. |
| Idle stop | An instance that serves no request for 15 minutes stops. The host can stop idle instances earlier when it needs memory. |
| Cold start | The first request after a stop starts the Worker again and waits for its startup. |
Monthly CPU budget
Workers CPU is measured across all projects in your organization, in CPU milliseconds: Free includes 3,000,000 ms (50 minutes), Starter 30,000,000 ms (500 minutes), and Pro 100,000,000 ms (1,666⅔ minutes) per calendar month. The budget resets at 00:00 UTC on the first day of each month. This measures CPU consumed after startup, including background work, rather than request duration or time spent waiting for I/O. Startup CPU is excluded.
Workers and Functions share the organization's execution admission: reaching the Workers CPU budget or either Function monthly allowance blocks new executions of both with 429 quota-blocked. Static assets served without Worker execution remain available. For example, after an organization uses its 50 Free CPU minutes, a Worker API route and a Function are blocked while an asset-only image can still be served.
Check your organization's Usage page. Wait for the monthly reset, or upgrade Free or Starter to a plan whose allowance covers your usage; Pro must wait for the reset. An upgrade changes the allowance, without clearing CPU already used this month. A short retry does not recover an exhausted budget. See Plans and limits for all monthly allowances.
Errors your visitors can see
When Eurobase answers instead of your Worker, the response carries an X-Eurobase-Gateway-Error header with the reason. A browser opening a page sees an error page; other requests get JSON with error, reason, requestId and retryable (true for 5xx).
| Status | Reason | Meaning |
|---|---|---|
| 429 | quota-blocked | The organization has exhausted its monthly Workers CPU budget or a Function request or execution allowance. New Worker and Function executions are blocked until the UTC month resets or an eligible plan upgrade provides enough allowance. Static assets served without running a Worker remain available. |
| 429 | project-concurrency-limit, org-running-limit | The host is busy and the project or organization already has its share: every cold-start slot is taken, or every request slot but the 16 kept for organizations that become busy. Retry after 5 s. |
| 502 | worker-terminated;cause=oom|watchdog|recycle-drain|crash | The instance stopped while answering: out of memory, busy CPU, a replacement, or a crash. |
| 502 | worker-response-header-too-large | The Worker sent response headers that are too large. |
| 503 | admission-full, memory-pressure | The host has no room for the request right now. |
| 503 | startup-timeout, worker-startup-failed | The Worker did not start in time, or failed while starting. |
| 503 | secrets-unavailable, runtime-secret-config-changed, secret-binding-conflict | The Worker’s secrets could not be loaded, or no longer match this deployment. |
| 503 | release-unavailable, release-invalid, release-digest-mismatch, release-binary-unavailable, gc-handoff-failed, quota-state-unavailable | A problem on Eurobase’s side. Retry later. |
| 504 | deadline-exceeded | The response did not start in time. |
Requests and logs
On a Workers project's deployments page, Requests and logs opens the Worker page with the last 24 hours. Requests lists each request with its status, outcome, duration and whether it was a cold start. Logs shows your Worker's console output, uncaught errors and platform events such as a stop. Each deployment can log about 100 lines per second, with bursts up to 1,000; lines over that rate are dropped and counted as Lines dropped. A line longer than 8 KiB is cut at 8 KiB.
Headers and paths
- Your Worker receives
X-Forwarded-Proto: httpsandX-Forwarded-Host, and, when Eurobase knows the visitor's IP address, oneX-Forwarded-Forand oneCF-Connecting-IPheader with it. IncomingX-Forwarded-*,Forwarded,CF-*,Proxy-*,X-Real-IP,True-Client-IPandX-Eurobase-*headers are removed first. - Paths under
/__eurobase/are reserved for Eurobase and never reach your Worker. - Cookies work, with rules that keep other apps on
eurobase.devfrom forging them; see Cookies in your app.
Runtime code generation
eval() and new Function() during a request are not supported on Workers for now. A typical error is Code generation from strings disallowed.
This affects Next.js MDX libraries that evaluate compiled strings while rendering, such as Pliny/Contentlayer's getMDXComponent. Compile MDX to importable modules at build time, or use a library that does. Compiling MDX into a string that is evaluated on each request does not avoid the limit.
Build logs warn about possible runtime code generation and name the bundled module. A warning alone does not stop a deployment; the candidate render check does.