23 June 2026

Maybe You Don't Need Next.js

A practical reflection on choosing Vite over Next.js when the product does not need SSR, server actions, React Server Components, or SEO.

I recently made the call to switch one of our web applications from Next.js to Vite.

And the more I thought about it, the more I realized that the issue was not really Next.js. Next.js is a great framework. The issue was that we had chosen it almost by default.

You know how it goes.

You want to build a React application today, and the first thing that comes to mind is Next.js. Before you know it, you are setting up the App Router, thinking about server-side rendering, dealing with React Server Components, and then adding "use client" everywhere because most of your application is actually client-side.

At some point, you have to pause and ask:

Why are we doing all of this?

That was the question I eventually had to ask myself.

We started seeing pods get OOMKilled in Kubernetes. Out of memory issues. And the strange thing was, we were not doing anything particularly server-heavy. No meaningful server-side business logic. No server actions. No serious SSR workload. No real React Server Components use case.

So why exactly were we running a full Next.js server?

The uncomfortable answer was simple: because we had picked the framework before properly interrogating the use case.

And thou shall not choose architecture by vibes alone.


The default framework trap

This is one of those things that happens quietly in engineering.

A tool becomes popular. Then it becomes the obvious choice. Then the obvious choice becomes the default choice. And before long, people stop asking whether it is the right choice at all.

Next.js has benefited from this, but it is not unique to Next.js. Every generation of frontend development has had its default answer.

At some point it was jQuery. Then Angular. Then Create React App. Now, for many teams, it is Next.js.

Again, that does not make Next.js bad.

It just means we need to be honest about why we are choosing it.

If you are building a public marketing site, a content-heavy platform, an e-commerce storefront, or anything where SEO, server rendering, image optimization, routing conventions, middleware, and server-side data loading matter, Next.js can be a very strong choice.

But if you are building an authenticated dashboard, internal tool, admin panel, SaaS application, or mostly client-side interface backed by APIs, then you may need not carry all of that machinery.

Sometimes Vite is enough.

Sometimes plain HTML is enough.

Sometimes the thing you need is far simpler than the thing the industry is currently excited about.

When "use client" is telling you something

One of the signs I ignored for too long was how often we were using "use client".

In the Next.js App Router world, "use client" is not necessarily a problem. There are valid reasons to use it.

But when almost every meaningful component in your application needs it, that is a signal.

It may be telling you that your app is not really benefiting from the server-first model.

It may be telling you that your application is an SPA wearing an SSR jacket.

And once you notice that, the question becomes very practical:

What exactly are we paying for?

Because architecture always has a cost.

Not just cloud cost, although that matters.

There is also cognitive cost. Deployment cost. Debugging cost. Runtime cost. Onboarding cost. Operational cost.

If your app does not need server-side rendering, React Server Components, server actions, edge middleware, or SEO-driven routing, then Next.js might be solving problems you do not have.

And when a tool solves problems you do not have, you still inherit the complexity of those solutions.

Vite was the better fit

For our use case, Vite made more sense.

We needed a fast React application. We needed routing. We needed API integration. We needed good local development. We needed tests. We needed a clean build artifact that could be served simply.

Vite gives us that.

No server runtime required for the frontend. No SSR path we were not using. No App Router complexity for an app that was already client-heavy. No need to keep a Node process alive in Kubernetes just to serve what is effectively a static frontend.

That simplicity matters.

And the migration was not as painful as I initially feared. Of course, changing frameworks in a live system is still an architectural decision. You need tests. You need confidence. You need to understand the blast radius.

But code is often cheaper to change than the operational assumptions around it.

The expensive part is usually not the migration itself. It is admitting that the original decision needs revisiting.

This is the bigger lesson for me.

A lot of engineering decisions are influenced by momentum. We see what the community is using. We see what big companies are talking about. We see what has the best docs, the loudest ecosystem, the most tutorials, the most conference talks.

And all of that can be useful signal.

But it is not a substitute for thinking.

The question should not be:

What is everybody using?

The question should be:

What does this product actually need?

If the product needs SEO, server rendering, and server-side composition, then fine. Reach for a framework that gives you those things.

If the product is a dashboard behind authentication, talking to APIs, with most logic in the browser, then maybe a client-side app is not some outdated pattern. Maybe it is exactly the right architecture.

Thou shall choose the tool that matches the work.

Not the tool that wins the discourse.

My current heuristic

This is how I now think about it.

If I am building something mostly static, I start with HTML, CSS, and JavaScript. There is no shame in simple tools.

If I am building a rich client-side application, especially one behind auth, I strongly consider Vite with React, Vue, Svelte, or whatever frontend library fits the team.

If I need SEO, server-rendered pages, server-side data fetching, file-based routing conventions, image optimization, middleware, or server actions, then I consider Next.js.

If I am building something where traditional request-response pages are enough, I also do not mind reaching for server-rendered frameworks. Not every product needs to become a full SPA.

The point is not to crown one tool as the winner.

The point is to stop treating any tool as automatic.

The real mistake

Looking back, the mistake was not choosing Next.js.

The mistake was choosing Next.js without enough resistance.

We did not pause long enough to ask whether its strengths matched our actual needs. We accepted the industry default and then worked around the parts that did not fit.

That is how you end up with a framework doing server-side things for an app that mostly wants to live on the client.

That is how you end up with Kubernetes pods running out of memory for a frontend that should probably just be static files behind a CDN.

That is how you end up with complexity that feels normal only because everyone else is carrying it too.

And maybe that is the part worth remembering.

Sometimes the best engineering decision is not adopting the most capable tool.

Sometimes it is removing the capability you do not need.

Next.js is excellent.

Vite is excellent.

HTML is excellent.

The wisdom is in knowing which one your product is actually asking for.

Comments