Why I Don't Start React Projects Without Next.js Anymore
3 min read
For years my React projects started the same way: create the app, install a router, install a data-fetching library, configure the build, then — days later — write the first actual feature. Next.js collapsed that preamble so thoroughly that starting a plain React project now feels like choosing to assemble furniture without the instructions. Here's what specifically changed my mind, and where plain React still wins.
What changed my mind
It wasn't the benchmarks; it was rebuilding this very site. A portfolio with a blog needs fast, indexable pages, an RSS feed, and one server-side contact endpoint. In plain React that's four architectural decisions and probably a second deployable service. In Next.js it's the default shape of the framework. Three shifts carry most of the value.
Shift 1: File-based routing
Plain React routing is a library choice plus a configuration file that grows forever:
// Plain React — routes as code you maintain by hand
<Routes>
<Route path="/" element={<Home />} />
<Route path="/writing" element={<Writing />} />
<Route path="/writing/:slug" element={<Post />} />
</Routes>Next.js replaces the configuration with the filesystem:
app/
page.tsx → /
writing/page.tsx → /writing
writing/[slug]/page.tsx → /writing/:slugThis sounds cosmetic; it isn't. The filesystem is the routing documentation — every
developer can find any page's code without reading a config, and code-splitting per route
happens automatically instead of via hand-placed lazy() calls. On teams, that
convention eliminates a whole category of "where does this live?" friction.
Shift 2: Rendering strategy as a per-page decision
Client-side rendering means every visitor downloads JavaScript, runs it, then sees content — and search engines see a mostly empty div. For dashboards behind a login, fine. For anything public, it's a real cost: slower first paint on mid-range phones and weaker SEO.
Next.js makes rendering a spectrum you choose per page: fully static (this blog post — generated once at build, served as HTML), server-rendered per request, or client-rendered where interactivity demands it. The framework's job is letting a marketing page and a live dashboard coexist in one codebase, each rendered the way it should be. (The server/client component split that powers this has its own decision rules — I've broken those down in a separate practical guide.)
Shift 3: API routes replace a second project
Small products constantly need a little backend: a contact form that sends email, a webhook receiver, a proxy that hides an API key. In the plain-React world that means standing up and deploying a separate service. In Next.js it's a file:
// app/api/contact/route.ts — deployed with the site, no second service
export async function POST(request: Request) {
const data = await request.json();
await sendMail(data);
return Response.json({ ok: true });
}This site's contact form works exactly this way — SMTP credentials stay server-side, and there is no second repository, pipeline, or server to babysit. For small-to-mid products, that's a genuine reduction in operational surface, not just convenience.
When plain React (Vite) still makes sense
- Internal tools behind a login. No SEO, users on fast machines, CSR is fine — Vite's simpler mental model and faster feedback loop win.
- A dedicated backend team already owns the API. If the backend exists, API routes are redundant and SSR may complicate deployment for little gain.
- Embedded widgets and micro-frontends. You're shipping a component into someone else's page; a framework wrapping your whole app has nothing to wrap.
- Teams allergic to framework coupling. Next.js is a commitment to Vercel-shaped conventions. Self-hosting works fine (this site runs on a plain Node server), but the gravitational pull is real and worth acknowledging.
The honest summary
The question isn't "is Next.js better than React" — Next.js is React, plus the fifteen decisions every real project makes anyway, pre-made well. When those decisions match your project (public pages, mixed rendering, light backend needs), starting from plain React means paying senior-engineer time to rebuild a worse version of them. When they don't match — internal tools, existing backends, embeds — skip the framework without guilt. I know which situation I'm in more often. That's why my default changed.