Skip to content

Next.js on Vite With Vinext 1.0: What I’d Check Before Migrating

Next.js on Vite With Vinext 1.0: What I’d Check Before Migrating

Short version for the impatient: Vinext 1.0 is good enough that I’d try it on a side project this week, and not good enough that I’d move a client’s production app without a few days of checking first. If you want to know why, read on.

A quick honesty note before anything else. I haven’t migrated a real app to Vinext yet. What you’re reading is me going through Cloudflare’s 1.0 announcement with a pen, separating the claims I can check from the ones I’d have to take on trust, and writing down the tests I’d run on one of my own projects. That’s a smaller job than a benchmark post. I’d rather do a small job honestly than invent a migration story.

Why care at all? Most of the client work I do sits on Next.js, and a few of those apps live on a cheap VPS because the hosting bill matters more to the client than the framework’s preferred platform. Anything that makes a Next.js app portable lands on my desk sooner or later. I also keep writing about build tooling (my Vite+ post is the most recent), so a Next.js-on-Vite project was always going to get my attention. If you want to see the kind of apps I’m usually shipping, they’re on my portfolio.

Next.js vs Vite is the wrong question

Search for “nextjs vs vite” and you’ll find a pile of comparisons that treat the two as rivals. They aren’t. Next.js is a framework: routing, rendering modes, caching, server actions. Vite is a build tool and dev server. You can build a router and a renderer on top of Vite, and plenty of frameworks do. Next.js just isn’t one of them. Next.js ships its own bundler, and I wrote about how its chunking behaves in my Turbopack post.

So when people ask whether Next.js uses Vite, the answer is no. What Vinext does is more interesting than a comparison table. It reimplements the public next/* surface so that your existing Next.js code runs through Vite’s pipeline instead. The Cloudflare team says it started in February as a week-long, AI-driven experiment by one engineer, and seven months later it’s at 1.0 with customers running it in production for high-traffic dynamic apps.

According to the announcement, it handles the App Router, the Pages Router and hybrid projects. That list includes React Server Components, Server Actions, API routes, route handlers, middleware and client-side navigation. It can deploy to Cloudflare Workers (including the free plan), Netlify, AWS Lambda or plain Node. The Pages Router support surprised me. I assumed a project like this would chase the shiny App Router features and ignore the large, older apps that are painful to migrate. Those older apps are the ones I get hired to rescue, so I’m glad somebody noticed them.

The migration path is two commands, straight from the announcement:

# Check whether your project is compatible
npx vinext check

# Set up Vite and deployment config, keeping your Next.js structure
npx vinext init

Notice what that second command promises: your project structure stays put. That’s a good sign, because it means a failed experiment is a git checkout . away.

What the 99% compatibility number does and doesn’t tell you

The headline claim is that test compatibility for the most important customer-requested features now passes 99%, excluding cache components. They also run the Next.js end-to-end suite against Vinext every night and keep their own thousands of tests across both routers, dev and production servers, and the Node.js and Workers targets.

I like that. A nightly run against the upstream suite is how you find out about drift before your users do. But I have two questions I can’t answer from the outside.

First, 99% of what? The number covers features Cloudflare picked as important to customers. My app’s strangest middleware isn’t in that set, and neither is yours. Second, the number comes from the vendor. That doesn’t make it wrong. It means I treat it the way I treat any vendor benchmark, which is as a reason to run my own test and not as a result.

There’s one line in the post I keep coming back to. The authors say that building an alternative revalidatePath is simple enough, and the difficulty is making sure it actually affects rendered pages, cache entries and future requests. That’s the right worry. A function with the same name and signature is easy to fake. A function with the same side effects across a cache layer is hard. This is precisely the kind of thing I’d test directly:

'use server'

import { revalidatePath } from 'next/cache'
import { db } from '@/lib/db'

export async function publishPost(id: string) {
  await db.post.update({ where: { id }, data: { published: true } })
  // Does this actually refresh /blog and /blog/[slug] after the deploy?
  revalidatePath('/blog')
  revalidatePath(`/blog/${id}`)
}

My plan for a function like that is boring. Deploy to a preview, call the action, then request the page twice with curl -I and compare the cache headers and the body. If the page is stale after the action returns, I’ve found the sort of gap a pass rate hides.

Cache warming is the idea I’d steal even if I never use Vinext

Here’s the part that made me stop skimming. If your site has a long tail of URLs, build-time prerendering turns into a queue. You call generateStaticParams() or getStaticPaths(), the build machine renders tens of thousands of pages one after another, and most of them will see almost no traffic. Meanwhile the handful of pages that matter finished rendering an hour ago and you’re still waiting.

The Vinext answer is to move that work off the build machine. In the deployment flow they describe, a new Worker version goes out to 0% of production traffic. Then the pipeline requests the chosen pages from that version, which fills the cache. Once it’s warm, you promote the version. Real users never hit a cold page on release day. You can also let Vinext add high-traffic pages to the list on top of whatever your Next.js code declares.

The command is short:

npx @vinext/cloudflare deploy --warm-cache

Compare that to the old way of limiting build time, which I’ve done on more than one client site:

// Before: render only a slice at build time and hope the rest is cheap on demand
export async function generateStaticParams() {
  const top = await getTopPosts(50)
  return top.map((p) => ({ slug: p.slug }))
}
export const dynamicParams = true

That works, but I’m guessing at which 50 matter, and the first visitor to every other page pays for the render. Warming the cache after the build and before the traffic shift removes the guess.

Now the caveat, and it’s a real one. The announcement describes warming in terms of Cloudflare’s network and the Workers deployment flow. I don’t know how much of it carries over to Netlify or Lambda, and the post doesn’t say. If you’re thinking of Vinext for portability, check whether the feature you want is portable too.

The use cache gap

The most honest paragraph in the announcement is about Cache Components. Next.js 16 made them a big part of its story, and Vinext today has only limited support for the "use cache" directive. Cloudflare says that most teams they talked to weren’t using Cache Components and didn’t treat support as a prerequisite for moving.

I believe them about their customers. I don’t believe that tells me anything about yours. A sample of teams that were already willing to try a Next.js reimplementation on Vite is a self-selected group. If your app already leans on "use cache", the first thing to do is find out how much:

grep -rn '"use cache"' app src --include='*.ts' --include='*.tsx' | wc -l

Zero is great. A dozen means you should read the Next.js docs on the directive next to Vinext’s compatibility matrix on vinext.dev and decide route by route. I’m not saying the gap is a dealbreaker. I’m saying it’s the one place where the announcement tells you outright that the numbers stop.

There’s a second, slower risk that the post is also open about. Next.js canary gets new commits every day, so every morning an agent reviews the diffs and opens tracking issues for anything that could affect Vinext, and every night the compatibility matrix is regenerated. When a test reveals a gap, agents reproduce it and propose a fix, and human maintainers handle the cases that need judgment about mapping Next.js onto Vite. It’s a clever setup, and it also tells me the project is chasing a moving target by design. That’s fine while Cloudflare funds it. It’s the bus-factor question I ask of any compatibility layer: if the sponsor loses interest, who keeps up with upstream? The code is open source at github.com/cloudflare/vinext, so you can at least see the pace of commits for yourself.

What I’d run before touching a client app

Here’s my own checklist, in the order I’d do it. None of it is original, and none of it is from the announcement beyond the commands.

Start on a branch and run npx vinext check. Treat whatever it prints as a to-do list and count the items. If the count is small, run npx vinext init and see whether the dev server boots. Then run your existing end-to-end tests against both builds. If you don’t have any, write five: login, a form that triggers a server action, a page that depends on revalidatePath, a middleware redirect, and one image-heavy page. Five tests that exercise real behavior will tell you more than any pass rate.

After that, compare responses between the two builds with something simple:

for path in / /blog /blog/some-post /api/health; do
  echo "== $path"
  diff <(curl -sI "https://old.example.com$path" | sort) \
       <(curl -sI "https://new.example.com$path" | sort)
done

Header differences on cached routes are where I’d look first, since that’s where a reimplementation is most likely to disagree with the original. The announcement says Vinext’s tracing works with existing OpenTelemetry and Sentry setups, so if you use either, trigger an error on purpose and confirm it shows up with a sensible trace. I’d trust a thing I’ve seen fail correctly more than a thing I’ve only seen succeed.

Should you move at all?

My honest read is split by situation. If you’re stuck on the Pages Router with a large app and a deployment target Next.js doesn’t love, Vinext is the first project I’ve seen that treats you as a first-class user, and I’d spend a day on it. If you want your Next.js app on Workers with cache warming, same answer. If you rely on Cache Components, or on platform-specific Next.js features I haven’t listed, wait and watch the compatibility matrix.

And if you’re happy where you are, stay put. A working deploy is worth more than a faster dev server, and I say that as someone who likes fast dev servers.

Here’s what I’d do this week. Pick the least important Next.js project you own, make a branch, and run npx vinext check. Paste the output into an issue, count the unsupported items, and grep for "use cache". That’s an hour of work, and by the end of it you’ll know whether a bigger migration is worth planning or whether you can close the tab.