Should I Use Next.js or React? A Component Census Answers It
Only 24 of 92 components here run in the browser. The ratio that decides the choice, plus two defects showing what a framework does and does not buy.
Use Next.js when pages have to be found, indexed, or fast on a cold visit; use React on its own when everything lives behind a login and the first paint is a loading state either way. Next.js is React with the routing, rendering and data layer already decided, so the question is not which library to learn — it is whether you want those decisions handed to you or kept.
Almost every answer to this question compares feature lists. This one compares a bill. This site is a Next.js 16 App Router storefront with 27 page routes, 110 available products and 326 blog posts, and only 24 of its 92 components are React components in the "runs in the browser" sense. That ratio is the whole argument, in both directions.
The short answer, by project shape
| Project | Pick | Why |
|---|---|---|
| Marketing site, blog, docs, storefront | Next.js | Indexable HTML at build time is the product |
| Internal tool, admin panel behind auth | React + Vite | Nothing to index; SSR buys you nothing |
| App with a public marketing surface and a logged-in product | Next.js | One build, two rendering modes, no second stack |
| Embedded widget shipped into someone else's page | React | You are a component, not a site |
| Static brochure, five pages, no data | Either | At this size the framework barely matters |
| You need streaming, partial prerendering, server mutations | Next.js | These are framework features, not library features |
The row people get wrong is the third one. Teams with a public site and a private app often build two codebases, then maintain two component libraries and two deploy pipelines to avoid "unnecessary" SSR on the app half. Next.js renders both from one tree, and the cost of the server half on a logged-in page is close to zero if you keep it out of the bundle — which is the next section.
What "using Next.js" actually costs: the component census
The App Router's default is a Server Component. Opting into the browser is a "use client" directive at the top of a file, and that directive is the only place the two worlds differ in practice. So the honest measure of how much React-in-the-browser a Next.js app still needs is: count the directives.
$ find src/components -name '*.tsx' | wc -l
92
$ grep -rl '"use client"' src/components | wc -l
24
Broken down by architectural layer — this codebase follows atomic design, so the layers are a real boundary:
| Layer | Client components | Total | What the client ones are |
|---|---|---|---|
| Atoms | 0 | 7 | Nothing. Buttons, containers, headings — all server |
| Molecules | 19 | 41 | Anything owning interaction state |
| Organisms | 5 | 33 | Header (mobile menu), the grids with filters |
| Templates | 0 | 11 | Pure layout, no state |
(Two client-side hooks live outside src/components — useOwnedProducts and useWishlist — so the directive appears in 26 files overall. The 24 above are components.)
Zero of seven atoms and zero of eleven templates run in the browser. The primitives and the page layouts are server-rendered strings. The 19 client molecules are exactly what you would predict: the FAQ accordion, the screenshot carousel, the preview modal, the edition picker, the forms, the wishlist button. Every one of them owns state a server cannot own.
That is the shape of the trade. Choosing Next.js did not mean giving up React — 24 files are plain interactive React and work the way they always did. It meant that the other 68 never ship their code to a browser at all. If your app's ratio would be inverted — if nearly every component owns state, because it is a dense interactive tool — then the server half is dead weight and plain React with Vite is the cheaper answer.
The measured payoff, and where it stops
The reason to pay for a framework is a number, so here are the numbers. Across the eight main page types, measured with Lighthouse 12 against a production build:
| Desktop | Mobile | |
|---|---|---|
| Performance | 100 on all 8 | 89–99 |
| Accessibility | 100 | 100 |
| SEO | 100 | 100 |
| CLS | 0 | 0 |
Method and the full table are in docs/superpowers/LIGHTHOUSE.md. Those come from static rendering: /, /templates, /templates/[slug], /pricing, /docs, /blog, /blog/[slug] and the seven category hubs are all generated at build time, so a visitor gets HTML with content in it and a crawler gets the same thing without executing anything.
Plain React cannot produce that. A Vite SPA serves a near-empty <div id="root"> and a bundle; the content exists after JavaScript runs. Google will usually render it eventually, and "usually, eventually" is a bad bet for 111 product pages and 326 articles. That is the single clearest decision line in this whole comparison: if organic search is a channel you depend on, the framework is not optional.
Where it stops is just as clear. Mobile performance is 89–99, not 100, and the reason is documented rather than hidden: on / and /templates the remaining gap is interactivity the storefront deliberately keeps. Next.js does not make a client component free. It makes the absence of one free.
Two defects that show what the framework does and does not buy
Both of these shipped here, and both are useful because they land on opposite sides of the line.
The one the framework could not save us from. The LCP element on /templates is the first product card's cover image, and it was loading="lazy" like every other card. The browser could not discover it until layout ran, which cost 1.67 s of load delay on mobile. The fix was a priority prop that only the first card of a page-opening grid sets, since eager-loading a 111-product grid would be far worse; /templates mobile performance went 85 → 92. No amount of server rendering helps here. The HTML was perfect and arrived early; the browser simply had not been told which image mattered.
The one that was purely a framework-boundary mistake. Header (site-wide) and useOwnedProducts (behind every templates grid) both imported @/lib/supabase/client at module scope. Because those are client components, that pulled @supabase/ssr plus auth-js — ~68 KiB gzipped, 255 KiB parsed — into the initial bundle of /, /blog, /docs and /pricing, pages with no account UI whatsoever. Every visitor to the blog downloaded and parsed an auth stack to render an article.
That second one is the characteristic Next.js bug. In a plain React SPA it would barely be a bug at all — there is one bundle, everything is in it, and you would never think to check. The App Router gives you a boundary worth defending, and then lets you leak across it with one import at the top of a file. Both halves of that sentence are true, and they are the real answer to "is Next.js more complex?" Yes: it adds a boundary you have to think about, in exchange for the only thing that makes 68 server components free.
The piece of Next.js with no React equivalent
Request-time code that is not a component. src/proxy.ts — the Next 16 proxy, formerly middleware — refreshes the Supabase session on every matching request so that Server Components see a valid session:
export async function proxy(request: NextRequest) {
let response = NextResponse.next({ request });
const supabase = createServerClient(/* … cookie get/set wiring … */);
await supabase.auth.getClaims();
return response;
}
export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|.*\\.(?:svg|png|jpg|jpeg|webp|gif|ico|webmanifest|html)$).*)",
],
};
getClaims() rather than getSession() is deliberate — it validates the JWT instead of trusting the cookie's contents. And the matcher is most of the engineering: it excludes static assets and the crawler routes, because a session refresh on sitemap.xml is pure latency.
In a React SPA this layer is a server you write and deploy yourself, or an API gateway, or a useEffect that refreshes a token and races the first render. It is not that React cannot do it. It is that React has no opinion about where it goes, so every team invents a different answer. Next.js 16's proxy and what changed from middleware covers the API itself.
Worth knowing: this file also carries a cost that is pure framework. It runs on every request, so if its environment variables are missing the whole site errors — not one route, the site. A library gives you fewer single points of failure because it gives you fewer shared layers.
Choosing, honestly
Pick React (with Vite) when: the app is behind a login, you want to own routing and data fetching, your team is small and already fluent in React, or you are shipping a component into someone else's page. "I want fewer concepts" is a legitimate engineering reason, and SPAs are not a mistake — React vs Next.js on the admin-dashboard case specifically is the version of this question where React often wins outright.
Pick Next.js when: pages must be indexed, first paint matters to a stranger, you want server-side mutations without building an API layer for each one, or you have both a public and a private surface. Also pick it when you are not sure — every Next.js app is a React app, so the knowledge transfers in full, and the reverse migration (SPA to framework) is the more expensive direction.
One thing that should not sway you: which one a template or starter is built in. On this site a licence covers every ready edition of a product, so the framework is a folder you open rather than a purchase you commit to — the React vs Next.js template editions post works through that separately. Across the 110 available products here, 104 ship a ready Next.js edition and 8 a ready React edition, which says more about what buyers ask for than about what is correct for your project.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
useState/useEffect throws at build | A Server Component tried to use a hook | Add "use client" to that file, or lift the state into a child that has it |
| A huge library appears in the bundle of pages that do not use it | A client component imports it at module scope | Move the import behind the boundary, or into a server-only module |
| Content missing from view-source | The route is client-rendered | Render it in a Server Component, or prerender the route |
window is not defined | Browser API referenced during server render | Guard it, or move the access into an effect |
| Hydration mismatch on dates or random values | Server and client rendered different output | Compute it once on the server and pass it as a prop |
| Site-wide 500 after adding a proxy/middleware | It runs on every request and a dependency is missing | Validate env at boot; narrow the matcher |
"use client" seems to do nothing | It must be the first line of the file, before imports | Move it to the top |
Frequently asked questions
Is Next.js harder to learn than React? It is React plus a boundary. If you know React you can ship a Next.js page on day one; what takes a week is internalising which side of the server/client line each piece belongs on. That boundary is also the thing you are buying.
Can I start in React and move to Next.js later? Yes, and components port almost unchanged — the work is routing, data fetching and auth, exactly the three things React left you to decide. Moving the other way is easier still.
Does Next.js lock me into Vercel?
No. next build runs anywhere Node runs, and this site's own build is a static output plus a handful of server routes. Some features are smoother on Vercel; none require it.
Do I still need to learn React if I am using Next.js? You are writing React the entire time. Components, props, state, effects — all of it. Is React a framework? covers why the distinction confuses people in the first place, and Server Components vs Client Components covers the one concept that is genuinely new.
Templates in this post
ASoc Fade (a barbershop site with booking), ASoc Fiscal (a payments and invoicing platform site) and ASoc Flow (a workflow-automation SaaS landing page) each ship a Next.js edition built the way this post describes — server by default, "use client" only where state lives.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates.
