deploy-2nth.2nth.workers.dev · field manual 001

How 2nth ships
to the edge

Workers vs Pages, the two-account split, and the pipeline every new 2nth project follows — written into a site that is itself deployed the recommended way. The strip below is not decoration: it is a live response from this site's own Worker, computed the moment you loaded the page.

statuscalling…
colo—
city—
country—
computed at—

↑ fetched from /api/edge — a route served by src/index.ts, not by a static file. This is the part a plain static host cannot do.

§01

Two products, one platform

Pages and Workers are two generations of the same idea: put your site on Cloudflare's network. Pages came first as a static-site host with bolt-on functions. Workers is the general compute platform — and since it learned to serve static assets natively, it does everything Pages does, plus everything Pages can't (Durable Objects, cron triggers, queues, real observability).

Cloudflare has effectively frozen Pages. New platform features land on Workers only, and the docs point anyone hosting many sites at Workers.

Why this matters to 2nth specifically The imbila account hit the hard cap of 100 Pages projects (101 exist — deploys started failing). The cap is identical on free and paid plans and Cloudflare does not routinely raise it. The Workers cap on the $5/month paid plan is 500. Same sites, five times the ceiling, on the platform that's actually moving.
§02

The decision, as a table

You are building…Deploy asBecause
A static site — Astro, plain HTML, a framework build Worker + assets One assets.directory line in config. Static file requests are free and don't count as Worker invocations.
A site that also needs an API, auth, form handler Worker + assets Same project: files served first, unmatched routes fall through to your code. This site is the example.
A pure API / service / MCP server Worker No assets block at all. Add D1/KV/R2/Queues as bindings.
Something needing a *.2nth.ai hostname before the zone moves Pages (exception) Pages subdomains work cross-account via CNAME from the imbila zone; Workers hostnames can't. Temporary, until the 2nth.ai zone cutover.
Anything touching an existing imbila project Leave it there Legacy stays in imbila. D1/KV/R2 state cannot move between accounts. Redeploys of existing projects are fine; nothing new is created there.
§03

The recommended pipeline

Every new project follows one path. Repo in the 2nth-ai GitHub org, config pinned to the 2nth Cloudflare account, built by Workers Builds on every push.

Repo
Lives in 2nth-ai org. wrangler.jsonc pins account_id.
Local dev
npm run dev → wrangler serves assets + Worker at localhost.
git push
Workers Builds clones, runs the deploy command. PRs get preview URLs.
Edge
Live at ‹name›.2nth.workers.dev. Custom domain after the zone cutover.

Escape hatch: npm run deploy ships from a laptop through the same config — useful for a first deploy or an emergency, and exactly what the pinned account_id makes safe.

§04

Anatomy of this very site

The whole deployment is one config file and one small Worker. This is the entire wrangler.jsonc:

wrangler.jsoncthe recommended shape
{
  "name": "deploy-2nth",                // = repo name = project name, always
  "account_id": "aa0c1ccd…129e39",   // pinned: the 2nth account, never imbila
  "main": "src/index.ts",             // the code half (optional for pure-static)
  "compatibility_date": "2026-07-19",
  "compatibility_flags": ["nodejs_compat"],
  "assets": { "directory": "public" }, // the static half — this replaces Pages
  "observability": { "enabled": true }  // logs on by default, always
}

And the Worker that answered the hero strip — requests for real files never reach it; only unmatched routes like /api/edge fall through:

src/index.tsthe compute half
export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname === "/api/edge") {
      return Response.json({
        colo: request.cf?.colo,      // which datacentre answered
        city: request.cf?.city,
        servedAt: new Date().toISOString(),
      });
    }
    return Response.json({ error: "No such route" }, { status: 404 });
  },
} satisfies ExportedHandler<Env>;
§05

The two accounts

2nth

aa0c1ccd09eb8e6a1d5003d12c129e39 · Workers Paid

  • All new build — client delivery + platform
  • Mirrors the 2nth-ai GitHub org
  • Workers-first · 500-Worker ceiling
  • Subdomain: *.2nth.workers.dev

imbila (legacy)

3db4fadd9630027337f8ae204d570e81 · at the Pages cap

  • All 101 existing Pages projects, untouched
  • All existing Workers + D1/KV/R2 state
  • Academy, experiments, personal zones
  • Nothing new is created here
§06

Failure modes, learned the honest way

Case file: 2nth-marketing, first connect attempt Imported into Workers Builds, cloned fine, failed at deploy. Cause: its config says pages_build_output_dir = "." — the Pages dialect. A Workers build runs wrangler deploy, which doesn't speak it. The fix when migrating any old repo: swap that line for assets.directory, move Pages Functions from functions/ into a Worker route, and pin the account_id. Until then the live site keeps serving from imbila Pages — a failed build in the new account breaks nothing.

Other traps for the migration file: Workers custom domains need the zone in the same account (wait for the cutover) · zone subscriptions like the old Pro plan don't move with a zone · D1/KV/R2 never crosses accounts — export and re-import, or leave the project where its data lives.