← All case studies Webflow → Astro

ZenML rebuilt 2,224 Webflow pages in Astro during a one-week hackathon

Alex Strick van Linschoten moved zenml.io off Webflow in the February 2026 Claude Code hackathon. The page counts 2,224 generated pages, 2,151 crawled URLs, and 2,255 built HTML files as different sets. A five-day Webflow cache header kept the old site up after DNS moved.

Alex Strick van Linschoten published this on the ZenML blog on 3 March 2026. It is a first-party account of rebuilding zenml.io, from Webflow to Astro on Cloudflare Pages, during the Cerebral Valley “Built with Opus 4.6” hackathon, 10 to 16 February 2026. He did the evenings after his day job. He did not win. Designer Zuri Negrín is quoted later. The repo he points at is public, github.com/zenml-io/zenml-io-v2.

The site had three hosts. Jekyll first, which left Zuri fighting the generator instead of designing. Webflow for about two and a half years, which let her edit visually and made the CMS fine for a startup. Then this. The Webflow → Astro playbook is content, URLs, and a recoverable cutover. His post is what happened when the CMS limits and the cache headers showed up on a 2,000-page site.

Why Webflow stopped being the cheap option

He says Webflow was a solid start, and he would not talk a startup out of it. The site then passed 2,200 pages across 20 CMS collections. No version control, no CI, no self-hosting. Code samples in posts needed Webflow-specific hacks. The LLMOps database, 1,453 entries of tools, frameworks, and research, needed faceted search with AND and OR, plus full text. Webflow’s CMS filter could not combine filters that way. Usage limits also pushed the bill up. The symptom he treats as decisive is softer. They put off messaging changes because editing Webflow had become a chore.

The money at the end is about $800 a year. He says that saving alone would not have justified the move, and that the Webflow bill was only going to grow with the CMS.

A crawl before a component

He would not start in Astro until he had a baseline.

The CMS export was 2,340 items across 20 collections. Assets were 2,397 images and files, 384 MB. A crawl of 2,151 live URLs captured every meta tag, canonical, and Open Graph field. That crawl was the contract. A changed title or a dead URL was a regression. ChatGPT 5.2 Pro’s deep research compared frameworks for a static content site of this size and landed on Astro, Cloudflare Pages, a few Preact islands, and Pagefind. He downloaded the Webflow code export as a reference for layout, not as something to paste.

Six phases had a definition of done: infrastructure, content export, schemas, all pages, SEO and redirects, forms and QA. A separate audit doc tracked where the work drifted from the plan. It caught asset regexes that missed URL families, validators that loaded zero entries after the MDX switch, and manifest counters that disagreed across reruns.

Day 1 finished infrastructure and the export. Day 2 he started sending Claude’s work through RepoPrompt to GPT 5.2 and Codex 5.3, and that is when MDX looked like it would break 1,904 files. Day 3 he started a side project because he thought he would finish early. He was wrong. Day 5 was Cloudflare detail. Day 7 the site was done, and he spent a night cutting the hackathon video in Remotion.

Claude Code with Opus 4.6 did most of the building, about 143 commits that week. RepoPrompt, a Mac app that also runs as an MCP server, assembled file maps so he was not hand-picking context for 20 schemas and 37 components. At phase boundaries a second model reviewed the package. After the phase 2 schema plan, that review found seven issues: schema assumptions that did not match the export, a slug author versus authors, missing validation gates, and 73 items with no SEO block. The phase plan was rewritten before page templates existed.

He tried git worktrees so several Claude sessions could run at once. Merge conflicts made that slower than one session. Playwright screenshots got layouts to about 80 percent, and the rest was spacing and breakpoints by hand. Webflow interactions do not survive the code export, so he recorded them, had frames described, and turned that into CSS. Wrangler failed about 32 percent of the R2 uploads, mostly concurrency and rate limits. He switched the uploader to Python, boto3, and a thread pool, and the rest finished in about 30 seconds.

The counts are three different sets

He warns you about this. 2,151 is the live crawl. 2,224 is pages generated. 2,255 is HTML files in the build, because pagination and utility pages are in the output and were not all live URLs. Twenty collections got Zod schemas. Fifty-two redirect rules. Nine Preact islands, and everything else is static. Build time about 33 seconds during the hackathon. Lighthouse, his measurement: performance 99 desktop and 97 mobile, accessibility 100, best practices 96, SEO 100.

Pagefind indexes at build time and searches the 1,453 LLMOps entries in the browser. After the hackathon the build crept toward two minutes. He split it into page generation, the Cloudflare adapter’s server build, and Pagefind.

“More like this” on each LLMOps page scanned all 1,453 entries, about 2.1 million comparisons. Inverted indexes from tag to entries, and industry to entries, cut about 13 seconds. Pagefind was walking all 2,255 HTML files. Only the LLMOps pages have data-pagefind-body, so --glob 'llmops-database/*.html' cut about 20 seconds.

Removing @astrojs/mdx made the server build slower, 72 seconds to 117, even with zero MDX files. He says the MDX integration changes how Vite resolves modules, and the Cloudflare adapter benefits. Removing the adapter to skip a 50 to 100 second server build made page generation about 40 percent slower and broke ESM resolution for 26 remark and rehype packages. The adapter’s prebuild of the module graph was helping the static pages too.

MDX, preview analytics, and a cache that outlived the DNS change

Astro does not check that a file in public/ exists. A missing /images/logo.svg is a runtime 404. Template URLs such as ${R2}/hash/file.svg hide from a regex audit. He hit that more than once.

MDX was the early fork. MDX v2 treats HTML as JSX. Webflow HTML has curly braces in prose, multi-line tables, raw tags, and mixed Markdown. He switched 1,904 files to .md and passed HTML through with rehype-raw. Schemas, globs, validators, and the build all changed with that call. A single post can still become MDX later if it needs a component.

import.meta.env.PROD is true for every astro build, including branch previews. Gating Plausible, GA4, and Segment on that flag sent real events from preview URLs. The fix is the hostname, window.location.hostname === 'www.zenml.io'.

Some Webflow CDN file ids returned 403 every time, including with a browser user agent. He does not call them rate limits. The code export held the same images under older ids that still downloaded. He says to download that export before you need it.

Webflow sends surrogate-control: max-age=432000, which is five days. After the DNS cutover, Cloudflare still had a 3.2-day-old copy and kept serving the Webflow site. The new site was deployed. Visitors saw the old one. Purging the Cloudflare cache was the fix. Separately, the Pages custom domain flipped to Inactive after the nameserver switch, and again later. Twice. He says that feels like a DNS bug when it is the cache and a dashboard toggle.

What they could not do in Webflow, and what they now own

Zuri did a design pass after the hackathon. The LLMOps hub got a sidebar, industry and technology filters, an AND/OR tag mode, Pagefind, and a WCAG pass. The Webflow version was AND only, with a flat list of 107 tags that often returned nothing. The blog gained a category sidebar, a reading progress bar, and a table of contents when a post is long enough. 251 of 280 posts qualify. Every pull request has a preview URL, which is how design review with Zuri sped up.

Posts now go through git. He describes asking Claude Code to turn a Desktop markdown file into a branch, frontmatter, images, and a pull request. They also own DNS runbooks, rollback, cache behavior, cookie consent, CSP reporting, build profiles, and a production checklist. His line is that you are taking on infrastructure. For ZenML that was the point. Zuri’s quote on the page is that the site is nicer to change, and that new layouts are no longer boxed in by Webflow.

The three things he would do earlier are the .md decision before any validator, parallel boundaries before worktrees, and caching as part of the plan rather than a surprise after the nameservers moved.