aeternum

We moved our Next.js app from Vercel to Cloudflare with vinext, and it got faster

DriveFlow runs on Cloudflare Workers with vinext since 27 October 2026. Here is what we measured against Next.js on Vercel, what we had to change for Workers, and what vinext still lacks.

DriveFlow has run on Cloudflare Workers since 27 October 2026, built with vinext instead of Next.js. Signed-in pages answer up to a third faster than they did on Vercel, and on a slow phone the main content shows up sooner on every page we tested.

DriveFlow is our platform for Swiss driving schools: 89 pages, 44 route handlers, a proxy.ts, Supabase auth and PDF invoices with a Swiss QR-bill. When we looked for someone who had moved an app like that, we found migration pull requests for demos and personal sites. So here is ours.

Why we moved our Next.js app off Vercel

One reason was money, the other was distance.

Vercel’s Hobby plan is for personal, non-commercial use. DriveFlow takes payments for driving schools, so going live meant Vercel Pro at $20 per developer seat per month.

Distance mattered more. Our Supabase database is in Zurich, and Vercel ran our functions in Frankfurt. proxy.ts calls supabase.auth.getUser() on every signed-in navigation, so each page an instructor opened waited on a trip from Frankfurt to Zurich and back. Cloudflare answers a Swiss visitor from its Zurich location, and the Worker runs there too.

Request path before and after the moveBefore: a visitor in Switzerland reaches a Vercel function in Frankfurt, which calls the Supabase database in Zurich and waits for the answer. After: the visitor reaches a Cloudflare Worker in Zurich, which calls the same database in the same city.On VercelVisitorSwitzerlandFunctionFrankfurt, fra1SupabaseZurichEach auth check in proxy.ts crosses the border and backOn CloudflareVisitorSwitzerlandWorkerZurich, ZRHSupabaseZurichThe same check stays inside Zurich
The path of a signed-in page. On Vercel the auth check ran in Frankfurt and waited on Zurich. On Cloudflare the Worker and the database sit in the same city.

What the move does not change is who you depend on. Vercel and Cloudflare are both US companies, and keeping processing strictly inside the EU on Cloudflare needs its Data Localization Suite, which is Enterprise only. The data that matters for residency was in Zurich before and still is. Where data is processed is the same question that decides whether you can run an AI model on customer data, which we went through in local AI models and Swiss data protection.

What vinext is, and how it differs from OpenNext

vinext is Cloudflare’s reimplementation of the Next.js API on Vite. It reads the same app/ directory, next.config.ts and next/* imports, and builds them into a Worker. OpenNext, the older route, takes the output of next build and translates it instead. We only tried vinext: 1.0 had just shipped, and npx vinext check rated DriveFlow 98 percent compatible. The missing two percent was Sentry’s build plugin.

The cost of vinext is plain. It is a second framework that has to follow every Next.js release, and it is young: 1.0 shipped on 28 September 2026, and we tested 1.3.0.

Vercel vs Cloudflare: the benchmark

Vercel ran the regular Next.js 16 build, Cloudflare the vinext build of the same commit, both against the same production database. A script requested each page 30 times with curl, alternating between the two hosts so both saw the same network, and signed in with real test accounts where a page needs it.

Server response timePaired bars for ten DriveFlow pages. Cloudflare has the lower median on every page. Public pages: landing 160 against 134 ms, discover 129 against 116, school profile 236 against 178, guide article 118 against 94, sitemap 85 against 73, login 118 against 87. Signed in: owner finances 185 against 125, instructor dashboard 166 against 137, instructor calendar 156 against 150, student dashboard 174 against 126.Server response timeMedian time to first byte in ms, 30 requests eachVercel, FrankfurtCloudflare, ZurichPublic pagesLanding pageLanding page, Vercel, Frankfurt: 160 ms160Landing page, Cloudflare, Zurich: 134 ms134DiscoverDiscover, Vercel, Frankfurt: 129 ms129Discover, Cloudflare, Zurich: 116 ms116School profileSchool profile, Vercel, Frankfurt: 236 ms236School profile, Cloudflare, Zurich: 178 ms178Guide articleGuide article, Vercel, Frankfurt: 118 ms118Guide article, Cloudflare, Zurich: 94 ms94sitemap.xmlsitemap.xml, Vercel, Frankfurt: 85 ms85sitemap.xml, Cloudflare, Zurich: 73 ms73LoginLogin, Vercel, Frankfurt: 118 ms118Login, Cloudflare, Zurich: 87 ms87Signed inOwner financesOwner finances, Vercel, Frankfurt: 185 ms185Owner finances, Cloudflare, Zurich: 125 ms125Instructor dashboardInstructor dashboard, Vercel, Frankfurt: 166 ms166Instructor dashboard, Cloudflare, Zurich: 137 ms137Instructor calendarInstructor calendar, Vercel, Frankfurt: 156 ms156Instructor calendar, Cloudflare, Zurich: 150 ms150Student dashboardStudent dashboard, Vercel, Frankfurt: 174 ms174Student dashboard, Cloudflare, Zurich: 126 ms126
Cloudflare had the lower median on all ten pages. The slowest tenth was mixed: ahead on signed-in pages, slightly behind on three public ones. Treat differences under about 15 ms as noise.

The largest gains are where we expected them. On signed-in pages the auth call is now a trip from Zurich to Zurich, and the owner’s finance page went from 185 to 125 ms.

In a real browser on a fast desktop connection the gaps were mostly small: the landing page drew its main content in 300 ms on Vercel and 308 ms on Cloudflare. The school profile was the exception, at 784 against 328 ms. On a throttled phone profile the difference became something you would notice.

Main content on a slow phonePaired bars for four public pages on a throttled phone profile. Landing page 2852 against 1640 ms, discover 2196 against 1552, school profile 2612 against 1696, guide article 2072 against 1444. Cloudflare shows the main content 29 to 42 percent sooner.Main content on a slow phoneLargest Contentful Paint in ms, slow 4G, CPU 4× slowerVercel, FrankfurtCloudflare, ZurichLanding pageLanding page, Vercel, Frankfurt: 2852 ms2852Landing page, Cloudflare, Zurich: 1640 ms1640DiscoverDiscover, Vercel, Frankfurt: 2196 ms2196Discover, Cloudflare, Zurich: 1552 ms1552School profileSchool profile, Vercel, Frankfurt: 2612 ms2612School profile, Cloudflare, Zurich: 1696 ms1696Guide articleGuide article, Vercel, Frankfurt: 2072 ms2072Guide article, Cloudflare, Zurich: 1444 ms1444
Median of three loads at 375 × 812 pixels, 150 ms latency, 1.6 Mbit/s and the CPU slowed four times. The full load event finished at about the same time on both.

Cloudflare sends more, though. On the phone profile the landing page was 818 KB in 102 requests, against 734 KB in 38 on Vercel. vinext splits JavaScript into many small chunks and prefetches the code of linked pages after load, so the first paint does not wait for it. The bytes are still real on a metered connection.

One setting is not optional. Our first build had image optimisation off, and the landing page sent 1,186 KB of images instead of 175 KB. With Cloudflare Images on, a 640 pixel screenshot came out at 23 KB as AVIF, against 24.5 KB as WebP on Vercel.

All of this comes from one machine in Switzerland on one evening, 26 October. It says a lot about Swiss users and nothing about anyone else.

What we had to change for Workers

Most of the work was not about vinext. It was about Workers, which is not Node.js: there is no file system and no child process, and fetch differs in places.

On Vercel On Cloudflare
pdfkit reads its font metrics from disk pdfkit’s browser build, with Helvetica registered at startup
Fonts and logos read with fs at request time The bytes inlined into the Worker at build time
The service worker built inside a route with esbuild and git Built by a small Vite plugin after the client build
fetch with redirect: "error" redirect: "manual", which Workers accepts and which still never follows a redirect
The client IP from x-vercel-forwarded-for cf-connecting-ip
Vercel Cron A scheduled handler in the Worker that calls the same route
Vercel Analytics and Speed Insights Cloudflare Web Analytics, without cookies
*.vercel.app previews are noindex by default Our proxy sends X-Robots-Tag: noindex on *.workers.dev

Before the switch we went through every signed-in page as owner, instructor and student, and generated every file the app produces on both hosts. The same invoice with its QR-bill, once from Vercel and once from the Worker, side by side. A 200 tells you nothing about whether a PDF looks right.

What vinext does differently, and what it does not do yet

vinext itself gave us three small surprises. It refuses to build two pages that resolve to the same path, which Next.js allows; ours was an unused page in a route group, and we deleted it. It writes its immutable cache rule for hashed chunks into _headers only when that file does not exist yet, so a plugin that creates the file first leaves every chunk at max-age=0; our build now merges both rules. And it calls register() in instrumentation.ts without setting NEXT_RUNTIME, so @sentry/nextjs never started on the server, and @sentry/cloudflare took its place. None of the three was in the issue tracker when we searched on 27 October.

A few open issues are worth reading before you switch. Requests can get an empty 503 in the seconds after a deploy. There is no skew protection, so a tab opened before a deploy can ask for chunks that no longer exist, although Vercel Hobby had none either. Server Actions break in tabs opened before a deploy, which did not affect us because DriveFlow uses none. And some headers() patterns match differently from Next.js, so we checked ours on the trial and will again after every change to next.config.ts.

Vercel vs Cloudflare pricing for a small team

Vercel Pro is $20 per developer seat per month, with $20 of usage included. Workers Paid is $5 per month for the whole Cloudflare account, and requests answered from static assets are not billed. At DriveFlow’s traffic both stay at their base price, so the difference today is $15 a month per seat, and it grows with the team and the traffic.

The free Workers plan is not an option for an app this size. Our server bundle is 4.5 MB gzipped, and Workers Free allows 3 MB against 10 MB on Paid. The build got a little faster as well: 26 seconds locally instead of 34, and 58 to 75 seconds from push to production instead of 85 to 100.

Should you move your Next.js app off Vercel?

If your users and your database sit in one region and Vercel runs your functions in another, the move pays off in latency before it pays off in money. If your app leans on Server Actions or needs skew protection today, wait for those issues to close.

If you try it, keep to this order. Run npx vinext check. Deploy the same commit to a workers.dev URL and mark it noindex first. Benchmark both hosts interleaved, not one after the other. Then compare everything that produces a file, from PDFs to share images, side by side. No test suite does that last step for you.

Was moving off Vercel worth it?

For us, yes. The app is faster for the people who use it, and it costs less than the Vercel plan we would have needed. The account model settled it. Inviting my colleague Nino to the Cloudflare account cost nothing, so the two of us work in one account on one Workers Paid plan, for $5 a month for now. Cloudflare Tunnel is free as well and gives an internal service a public address without opening a port. So we did not stop at DriveFlow: the rest of our infrastructure moved to the same account.

Moving a production app without its users noticing is the kind of web development work we take on for agencies. The audit DriveFlow went through before its pilot is in what we check before a real business uses it.

The service behind this

Web Development in detailMost of the cost is in the second year, so we build for it.

Questions about this.

Is vinext ready for production?

We have run DriveFlow on it in production since 27 October 2026, and it held up. It is young, though: 1.0 shipped on 28 September 2026, releases come roughly weekly, and there are open issues around deploys, such as empty 503 responses right after a deploy and no skew protection. Check the open issues against your own app before you switch.

What is the difference between vinext and OpenNext?

OpenNext takes the output of next build and translates it into a Cloudflare Worker, so the real Next.js compiler stays in the loop. vinext is Cloudflare's reimplementation of the Next.js API on Vite and builds the Worker itself. It reads the same app directory and next.config.ts, but the framework underneath is a different one that has to keep up with Next.js.

Is Cloudflare faster than Vercel for Next.js?

For us, measured from Switzerland, yes: a lower median time to first byte on every page we tested. Most of that comes from the Worker running in Zurich next to our database, while Vercel ran our functions in Frankfurt. With users or a database elsewhere, the result can be different.

How much does Cloudflare Workers cost compared to Vercel?

Workers Paid is $5 per month for the whole account, with 10 million requests and 30 million CPU milliseconds included, and requests served from static assets are not billed. Vercel Pro is $20 per developer seat per month with $20 of usage included. Both prices are from the pricing pages on 26 October 2026.

Can a Next.js app run on the free Workers plan?

Ours cannot. Its server bundle is 4.5 MB gzipped, and Workers Free allows 3 MB against 10 MB on Workers Paid. The free plan also limits each request to 10 ms of CPU time, which is tight for server-side rendering.

Tell us what you want to build.

Building something in this direction? Tell us what you want to build.

Keep reading

  1. What we check before a real business uses itProduct5 min read
  2. The Swiss case for running a model on your own hardwareAI6 min read
  3. Anthropic left git. The reason is the part worth keeping.Engineering6 min read