Next.js vs React Router: Website or Web App?
Next.js vs React Router: which should you choose? It may come down to one simple question: are you building a website or a web app?
Next.js vs React Router: Are You Building a Website or an App?
If you had asked me a few years ago to compare Next.js and React Router, the comparison would have been fairly straightforward.
React Router was, well, a router. You added it to a React application when you needed multiple routes.
Next.js was a framework. It gave you routing, server-side rendering, static generation, backend capabilities, optimizations and a whole collection of opinions about how a React application should be structured.
That comparison doesn’t really work anymore.
React Router has grown up considerably. In Framework Mode, it now supports data loading, actions, code splitting, server-side rendering, static pre-rendering and SPA rendering. It can be much more than the routing library many React developers remember.
So now we have two very capable React frameworks. Both can build websites. Both can build SPAs. Both can render on the server.
Which one should you use?
I think there’s a simpler way to make that decision:
Are you primarily building a website or an application?
That’s usually where I would start.
Next.js feels at home on the web
Let’s say I’m building something like this:
/
/about
/products
/products/widget
/blog
/blog/nextjs-vs-react-router
/contact
Those URLs aren’t merely different screens inside an application. Each one is a destination.
Someone might land directly on /products/widget from Google. Another person might open a blog article from LinkedIn. A search engine needs to understand those pages. They need titles, descriptions and social metadata. Initial page delivery matters.
This is where I think Next.js makes a lot of sense.
Next.js describes itself as a framework for building full-stack React applications, so calling it simply a “website framework” would be unfair. But many of its strengths line up extremely well with website-oriented projects.
Rendering, layouts, metadata, caching, images and server functionality are integrated into the framework. You don’t have to assemble these pieces yourself.
For a marketing website, publication, product website, documentation portal or content-heavy e-commerce storefront, that’s useful.
You can build all of those with React Router. The question is whether I’d choose to.
Usually, I wouldn’t.
React Router feels at home inside an application
Now imagine a different route structure:
/dashboard
/customers
/customers/123
/projects
/projects/456
/reports
/settings
Technically those are still web pages.
But that’s not how the user thinks about them.
A user signs into the product, lands in the application and starts working. They move between customers, open projects, change filters, submit forms, edit records and watch data update.
Google indexing /customers/123 probably isn’t high on the requirements list. In fact, you’d probably be rather concerned if Google did index it.
This is where I find React Router particularly attractive.
React Router itself describes the framework as being well suited to Single Page Applications, and its current Framework Mode lets you explicitly disable runtime SSR with ssr: false. But importantly, you’re not stuck there. React Router also supports SSR and static pre-rendering if you need them later.
That flexibility changes the conversation.
I can start with an application that is fundamentally client-driven without pretending every screen needs to be a server-rendered page.
Especially when you already have a backend
This distinction becomes even clearer when the frontend isn’t your backend.
Suppose your architecture looks something like this:
React application
↓
REST / GraphQL API
↓
Express / Laravel / Django / .NET
↓
Database
In that architecture, I often prefer keeping the frontend exactly that: the frontend.
React Router handles routes, layouts and the application’s navigation model. TanStack Query or another data library can handle server state. My API remains responsible for business logic.
It’s a clean boundary.
Next.js can absolutely work with a separate API. Nothing prevents it.
But this is where I start asking whether I actually need the additional server-oriented machinery that Next.js brings to the project.
Sometimes I do.
Sometimes I’m solving problems I didn’t have.
React Router isn’t “just a router” anymore
This point is worth stressing because I suspect a lot of developers still mentally compare modern Next.js with the React Router they used five years ago.
That’s not really the product anymore.
React Router currently offers Declarative, Data and Framework modes.
You can use it as a relatively lightweight routing library. You can add loaders and actions. Or you can use the full framework with type-safe route APIs, code splitting, SPA rendering, SSR and static pre-rendering.
The interesting part is that you get to decide how far you want to go.
Want a traditional SPA? Fine.
Want to pre-render a handful of public routes but leave the application client-rendered? You can do that too.
Want full server-side rendering? That’s available as well.
The boundaries between Next.js and React Router have become considerably fuzzier.
Some product teams are making this choice too
This isn’t only an architectural thought experiment. There are some interesting examples of teams building serious web products reconsidering Next.js.
One of the best examples comes from Flightcontrol.
The team rebuilt its dashboard using the Next.js App Router and later published a remarkably candid article called Next.js App Router migration: the good, bad, and ugly.
And the word dashboard matters here.
Flightcontrol wasn’t building a blog. It was building an interactive product with nested layouts, live data and complex application state.
The team had considered Next.js App Router, Remix and TanStack Router. Next.js looked like the safest long-term choice at the time, particularly with React Server Components appearing to represent the future direction of React.
Their experience wasn’t entirely negative. They liked things such as nested layouts and some aspects of initial server data loading.
But they also ran into complexity, development-server performance problems, debugging difficulties and duplication between server-side loading and the client-side fetching they needed for live updates.
Their eventual conclusion was unusually direct:
“We’d go back and choose Remix if we could.”
That’s a pretty interesting statement from a team that had actually built and shipped the product.
Then there’s ChatGPT
ChatGPT makes this discussion even more interesting.
ChatGPT originally launched on Next.js using the Pages Router. In 2024, its frontend was observed moving to Remix. When Remix’s framework functionality subsequently converged with React Router, ChatGPT moved along with it.
Today, ChatGPT’s web frontend has been identified as using React Router in Framework Mode.
There is an excellent independent technical analysis of this architecture in Reverse Engineering ChatGPT Web: How OpenAI Built for a Billion Users.
An important disclaimer: this isn’t an official OpenAI engineering article. The author reverse-engineered the public ChatGPT frontend, bundles and network behavior, so we shouldn’t pretend to know OpenAI’s internal reasoning for the migration.
But the architecture itself is fascinating.
ChatGPT is almost the perfect example of what I mean by an application-first experience.
You don’t primarily use ChatGPT by jumping between independently consumed pages. You enter the product and stay there. You create conversations, switch between them, upload files, change settings and continuously interact with backend APIs.
Interestingly, the analysis found that ChatGPT uses TanStack Query extensively for client-side server state alongside React Router.
And yet there’s an important twist: the current ChatGPT architecture also uses server rendering.
That actually strengthens the point.
Choosing React Router doesn’t mean choosing “client rendering forever.” An application can be application-first while still using SSR where it provides a real benefit.
So is Next.js only for websites?
Absolutely not.
You can build a fantastic SaaS dashboard with Next.js.
Likewise, React Router isn’t only for SPAs. Its current rendering options include client-side rendering, server-side rendering and static pre-rendering.
That’s why I wouldn’t make the decision using a feature comparison table.
Both sides of that table are going to contain a lot of checkmarks.
Instead, I’d look at the center of gravity of the product.
If I’m building a marketing site, blog, documentation portal or public content platform, my default choice would probably be Next.js.
If I’m building an admin panel, CRM, management system, analytics dashboard or a SaaS application sitting on top of an existing API, I’d lean toward React Router.
And then there are hybrid products.
Imagine:
/
/pricing
/blog
/login
/app/dashboard
/app/projects
/app/settings
Now the answer becomes less obvious.
Next.js might be attractive because the website and application can comfortably live together. React Router might still make sense if the application represents 95% of the actual product.
There’s no framework rule that can make that decision for you.
My rule of thumb
When choosing between the two, I wouldn’t start with:
“Does React Router support SSR?”
It does.
I wouldn’t start with:
“Can Next.js build an SPA?”
It can.
And I definitely wouldn’t choose based on which framework happens to be getting more attention on X this month.
I’d start with one question:
What am I actually building?
If the answer is primarily a website — a collection of public destinations where content, metadata, search engines and initial page delivery are major concerns — I’d reach for Next.js.
If the answer is primarily an application — somewhere users sign in, stay for a while, manipulate data and continuously interact with APIs — I’d seriously consider React Router.
Neither choice locks you into one rendering model anymore.
And perhaps that’s the most interesting part of the Next.js vs React Router debate today.
The question is no longer which one can do more?
They can both do a lot.
The better question is which one gets out of your way for the thing you’re actually trying to build?