bytePounce

How We Moved to Next.js and Cloudflare Workers

We moved our website from a hand-coded static HTML/CSS site to a full Next.js application deployed on Cloudflare Workers. Here's why, and what we learned.

Mario Andrei31 Mar 20267 min read
How We Moved to Next.js and Cloudflare Workers

The old bytepounce.com was a static HTML/CSS/JS site built by hand. It worked. It was fast. But it was getting hard to maintain, and adding new pages meant copying boilerplate and hoping nothing broke.

We decided to rebuild it properly: a Next.js application deployed on Cloudflare Workers. Here's what that looked like and what we learned along the way.

Why we chose Next.js for a business website

We evaluated a few options. Astro was a serious contender for a mostly-static site. WordPress crossed our minds briefly and was immediately dismissed. Next.js won because:

  • App Router + React Server Components: components that fetch data and render on the server, without shipping unnecessary JavaScript to the client
  • File-system routing: no routing configuration files, just folders
  • Static generation: pages with no dynamic data are pre-rendered at build time, making them as fast as a static site
  • TypeScript out of the box: the project is fully typed from day one

We weren't trying to build a complex app. We wanted a fast, maintainable site with a good developer experience. Next.js gave us that.

The App Router also gives us flexibility to add dynamic features without rebuilding from scratch. The contact form, the blog, the Cal.com booking integration: all of it runs within the same framework. That matters when you want to avoid maintaining multiple separate systems as the site grows.

Why Cloudflare Workers beats a VPS

Our previous site ran on a basic VPS. Cloudflare Workers replaced that with:

  • Edge deployment: the site is served from Cloudflare's global network, not a single server
  • No cold starts: Workers run on V8 isolates, not containers, so there's no startup latency
  • Workers KV and runtime secrets: environment variables (API keys, secrets) are stored securely without a .env file in production

The adapter we used is @opennextjs/cloudflare. It takes a standard Next.js build and wraps it for the Workers runtime. The adapter is actively maintained and handles the gap between Node.js APIs and the Workers environment reasonably well, though you do occasionally run into constraints.

Running on a VPS means paying for uptime, managing OS updates, and occasionally SSHing in to debug something. Workers has none of that. You push code, it runs. The operational overhead is essentially zero once it's configured.

On cost: the site runs within Cloudflare's free tier with room to spare. Even on a paid plan, it doesn't compare to what a managed VPS or managed WordPress hosting would cost.

The stack in full

Framework:      Next.js 16 (App Router, TypeScript)
Styling:        Tailwind CSS v4
UI components:  shadcn/ui
Animations:     Framer Motion
Forms:          React Hook Form + Zod
Bot protection: Cloudflare Turnstile + honeypot
Email:          Brevo transactional API
Analytics:      Umami (privacy-first, cookieless)
Booking:        Cal.com embed
Deployment:     Cloudflare Workers via @opennextjs/cloudflare

A few notes on the choices:

Tailwind CSS v4 is a significant departure from v3. Configuration moves out of tailwind.config.js and into CSS. The upgrade cost us a few hours mid-project; starting with it from day one would have been smoother.

Umami for analytics was a deliberate privacy choice. No cookies, no GDPR consent banner, no data sent to Google. The dashboard is simple and the data is ours.

Brevo handles transactional email for the contact form. We chose it partly because it lets you disable IP authorization, which is required when sending from Cloudflare Workers since Workers have no fixed egress IPs. Postmark and SendGrid don't make this easy.

The migration process

The actual build took a few weeks of evenings and weekends. The design goal was to match the original site exactly, which sounds simple but requires discipline. It's very easy to start "improving" things during a rebuild and lose weeks. We didn't.

Pages were migrated one at a time: Home, Services, Portfolio, Team, FAQ, Contact. Each page got proper TypeScript types, server components where possible, and Framer Motion animations matching the originals. SVG handling turned out to be trickier than expected. Next.js <Image> strips embedded styles from SVGs, which breaks animated SVGs entirely. Plain <img> tags for icons, <object> tags for animated illustrations.

The blog was added as a new feature rather than migrated (the old site didn't have one). MDX files in a content/ directory, rendered server-side with next-mdx-remote, syntax highlighting via rehype-pretty-code. The full pipeline took about a day to set up once we knew what we wanted.

The contact form required a bit more thought. Brevo for email delivery, Cloudflare Turnstile for bot protection, a honeypot field for the simplest bots that don't bother with JavaScript challenges. The whole thing runs through a Next.js route handler with Zod validation on both client and server.

This is the kind of work we do for clients too. If you're looking at a similar build for your own site, take a look at what we offer.

What we'd do differently

A few things we'd change in hindsight:

Start with Tailwind v4 from day one. We upgraded mid-project and the breaking changes in how plugins and design tokens work cost us a few hours.

Set up MDX from the start. This blog section was added later. Had we known we'd want it, the content pipeline would have been part of the initial architecture.

Document the gotchas as you hit them. We kept a running CLAUDE.md file with notes like "SVG icons must use <img> not <Image>." That file saved hours on subsequent work. Start it on day one.

Plan for Cal.com's embed requirements early. Cal's embed script needs a proxy queue on window before it loads. Without the official bootstrap snippet it throws "Cal is not defined" and crashes React hydration. It's a one-time setup thing, but it's not obvious from the docs and the error is confusing when you first hit it.

The deployment workflow: git push and done

CI/CD runs entirely through Cloudflare's GitHub integration. Push to master -> Cloudflare rebuilds and deploys automatically. No manual deploy commands, no SSH, no downtime.

Runtime secrets (Brevo API key, Turnstile secret) are set once via wrangler secret put and survive all future deployments. They never touch the codebase.

Build time is around 2 minutes. Including propagation across Cloudflare's edge network, the new version is live within 3-4 minutes of a git push. That's a fast feedback loop.

One thing worth noting: don't run wrangler deploy directly. The Cloudflare CI/CD pipeline handles environment variable injection and build configuration that a local deploy doesn't replicate correctly. Push to git, let Cloudflare build it.

Performance

The resulting site scores 98-100 on Lighthouse across all pages. Pages are statically generated at build time and served from Cloudflare's edge, so time-to-first-byte is consistently under 100ms globally.

That's faster than most managed WordPress sites, and cheaper to run.

The performance comes mostly from the architecture: static generation means there's no server-side rendering work per request, and edge deployment means the response comes from a node close to the visitor. The code quality matters less than people assume for these numbers, as long as you're not doing obviously wasteful things like blocking rendering with large JavaScript bundles.

The takeaway

If you want a marketing site or consultancy website that's fast, maintainable, and cheap to operate, Next.js on Cloudflare Workers is a solid combination. It has a steeper learning curve than WordPress, but the operational overhead is near zero and you're not fighting the platform.

The result is a site we can update quickly, extend confidently, and trust to stay up without watching it.

This is now the default stack we reach for when building websites for clients. If you're considering a similar migration for your own site, reach out. We've done it and we're happy to help.

Do you have a project for us?

Send us a message

Contact us