Engineering
523 Routes Moved Without a Rewrite
Three Workers preserved one URL surface; the audit rejected a provider-speed claim.
AI Engineer moved from Vercel to three independently deployable Forge Workers, preserving 523 route contracts and exposing the limits of its performance evidence.
The classic conference archive returned HTTP 200 and still looked broken. Its HTML came from the expected retained origin, but 18 generation-prefixed asset requests went elsewhere, leaving a page of raw links without its intended typography, layout, or images.
That failure changed the migration test. A green homepage—or even 523 green status codes—could not prove that pages, assets, redirects, bindings, and rendered states still belonged together. The public hostname concealed three applications with different source roots and release histories.
We moved AI Engineer from Vercel to three independently deployable Workers without changing its public URL surface. The final production audit covered 523 registered or compiled route contracts: 521 owned pages returned HTTP 200, and two photo routes ended at their expected 307 redirects to Pixieset. The migration made route ownership explicit and exposed a missing binding, broken internal links, an unnecessary archive runtime, and a Forge cold path that still needed work.
The useful result was not a provider-speed headline. One paired sample was effectively even; another favored Forge by 10.2%. Correctness—not a fragile winner—was the durable outcome.
One hostname hid three applications
The source repository, aiDotEngineer/aiecode2025, presents one site but contains three production applications:
apps/mainowns current conferences, shared pages, tools, and APIs.apps/2025-sitesowns the frozen 2025 conferences.apps/2023-2024-sitesowns Summit 2023 and World's Fair 2024.
Combining them into a new Next.js tree would have turned a hosting migration into an application rewrite. Forwarding archive traffic through baked preview URLs also failed the ownership test: every archive rebuild could change its Next build ID, forcing main to know which archive generation it was proxying.
We instead created three Forge Deploy projects in one repository. Each project owns its source root, build artifact, preview, production pointer, rollback state, and Cloudflare target.
apps/2025-sites
/worldsfair/2025* · /code/2025*/paris/2025* · /summit/2025*
/2025-static*
apps/2023-2024-sites
/summit/2023* · /worldsfair/2024*/2023-2024-static*
apps/main
every remaining path
The repository map is static JSON, so Forge can inspect project roots and proposed bindings without executing repository code. It is intentionally not DNS authority. Importing the file cannot enable a project, deploy a Worker, or attach a route; an approved provider target and an explicit administrator Apply action remain separate control-plane steps. That boundary is part of the repository configuration contract.
Main uses the lockfile-pinned @opennextjs/cloudflare@1.19.11 adapter because it still has a dynamic application surface. Forge validates the generated Worker and assets, then publishes through its connected-Worker path. After a later audit found that the archives were paying for runtime behavior they did not use, both archives moved to output: "export" behind small static Workers.
Asset paths fixed the routing shape
The archive pages are coupled to generation-specific chunks and data beneath /2025-static and /2023-2024-static. A page and the static prefix named by that page must reach the same Worker version.
The failure is visible when the classic archive is opened directly, outside the unified route topology. Its HTML returns 200, but 18 prefixed asset requests fail and the page loses its styling. Through www.ai.engineer, Cloudflare sends the page and the prefix to the same archive Worker.
Correctness needed a route contract
A green homepage would have proved almost nothing. We built the inventory from the sitemap, compiled outputs, redirect rules, conference registry, and a bounded same-origin crawl. It contained 443 concrete HTML routes, including 319 archived session pages, plus 80 literal redirect sources. One redirect family and two open-ended map-sharing families could not be exhaustively enumerated without known IDs.
After the three production pointers finished their independent promotions, the final pass requested all 523 literal routes through www.ai.engineer. It recorded zero transport errors. At the owned boundary, 521 pages returned 200 and /photos plus /worldsfair/2026/photos returned the expected 307 to Pixieset. A collector that followed those redirects received Pixieset's challenge on both hosts; a real JavaScript browser loaded the gallery and images. The contract now stops at the owned 307 and tests the external destination separately.
The same audit checked 18 documented machine-readable surfaces and 72 representative top-level browser navigations. All returned 200, and visual inspection showed the main, schedule, 2025, and classic layouts at desktop and throttled mobile widths.
The wider crawl also found defects that were not caused by the cutover:
/api/radio/station/radioreturned 503radio_worker_not_configured; the page rendered but said “Station unavailable.”- Eight internal links pointed to missing policy, conduct, or malformed archive paths on both hosts.
- Two Europe schedule URLs returned different pages without canonical tags.
- The first static-archive previews were marked Ready but returned the platform's plain-text 404 because the edge rejected their mixed
httpandassetsroutes before dispatch. Forge had to forward both route kinds to the same exact Worker version.
These failures changed the acceptance test. A route migration is complete only when pages, their assets, redirect boundaries, required bindings, and representative rendered states agree.
The benchmark changed its answer
We ran two paired server-timing experiments against the Forge hostname and retained Vercel deployment URLs:
| Sample | Comparable routes | Forge faster | Vercel faster | Median Forge/Vercel total-time ratio |
|---|---|---|---|---|
| Broad route inventory, two samples | 517 | 254 | 263 | 1.005× |
| Discoverable crawl, five samples | 126 | 81 | 45 | 0.898× |
The broad pass was effectively even at the median. The deeper pass put Forge about 10.2% lower. A conclusion that changes with the sampling plan is a diagnostic, not a provider verdict.
The limitations are material. The providers served logically related migration snapshots, not byte-identical commits. Measurements came from one macOS client, mostly through one edge region. The direct archive origins lacked the old unified router. The three Forge projects also promoted independently during collection, so we recorded each window and repeated the full correctness pass after their pointers settled.
There was no valid post-cutover Chrome UX Report window yet. Synthetic mobile results instead pointed to application weight: ten oversized PNG cards totaled 4.134 MiB. Converting them to bounded JPEGs cut those files to 0.178 MiB. Three same-machine Lighthouse 13.4.1 runs reduced median transfer from 6.40 MB to 1.27 MB and median LCP from 12.81 seconds to 5.49 seconds. That is directional lab evidence for a media patch, not proof that the hosting migration improved real-user performance.
Cache labels had hidden different work
The first build comparison mixed cache states. We corrected it with exact-SHA pairs within each provider:
| Build state | Vercel main | Forge main | Work still performed |
|---|---|---|---|
| Cold or snapshot-populating full build | 219.779s | 1,204.782s | Install, application build, and publication; Forge also created its first dependency snapshot |
| Restored-cache full build | 166.658s | 641.829s | Forge restored 367,697,920 bytes in 8.851s, then repeated checkout and OpenNext |
| Finished-artifact reuse | Not captured | 41.252s | Forge verified and reused an immutable provider artifact; it did not rebuild or republish |
The Forge dependency-warm run was 46.7% faster than its cold run, but it still reached Ready in 641.829 seconds versus 166.658 seconds for the Vercel pair. Those Ready boundaries and cache payloads differ, so the gap identifies Forge work rather than a provider ranking. Exact-source lookup missed, checkout consumed 167.657 seconds, and OpenNext still ran. Interrupted Forge run 936 is excluded because a Sandbox Durable Object reset stopped it during source transfer.
Finished-artifact reuse is a third condition, not a flattering name for a warm build. It resembles promotion of an already-built deployment. We did not capture the equivalent Vercel operation, so the 41.252-second result cannot support a cross-provider claim.
A frozen archive should build like one
The classic archive's initial post-build provider tail took 438.540 seconds. About 355.5 seconds of that seeded 248 OpenNext incremental-cache objects—14.35 MB—into R2. Actual Worker publication took about 12.5 seconds. Forge was faithfully preparing an incremental rendering cache for an editorially frozen site.
We removed the runtime work instead of tuning it. The two archives became deterministic Next static exports: 336 generated pages and eight redirects covered all 344 registered archive contracts. A small Worker preserved redirects, trailing slashes, headers, the branded 404, and the static prefixes.
The follow-up evidence has a narrower boundary than the live migration. At source SHA 286f56c, public Forge previews passed all 344 archive contracts and 756 referenced assets and Next data files. Classic's post-job provider tail fell to 92.320 seconds. Those candidates were verified but not promoted during the follow-up, so this is preview proof of the corrected architecture, not a claim about the active production pointers.
Ownership was the durable result
The migration forced Forge to make five boundaries explicit: monorepo roots own independent releases; the repository pins the OpenNext toolchain; domains and path routes are project-scoped desired state; path collisions fail closed; and exact source plus complete configuration identity can select a verified immutable artifact. Those boundaries later became part of the composed Forge Sites service stack.
That contract matters more than a narrow timing win. It lets an operator answer which project owns a URL, which source and artifact serve it, what can roll back independently, and whether a “warm” deployment skipped a build or merely restored dependencies.
The unresolved work is also explicit: restore the radio binding, repair the eight broken links and canonical conflicts, reduce exact-source checkout and the repeated provider compiler pass, investigate the interrupted Sandbox transfer, promote the verified static candidates after review, and wait for a full post-cutover CrUX window before making a field-performance claim.
The migration did not prove that Cloudflare is faster. It proved that 523 old URL contracts can move without an application rewrite when routing, assets, releases, and evidence share the same ownership model.
The public operating contracts are the Forge Deploy specification, repository configuration, and migration guide.
