The Case Against SPAs in 2026

I build single-page applications for a living. My own portfolio is plain HTML. That's not a contradiction.

Published Feb 25, 2026
Reading Time 8 min read
Tags
Architecture Performance Opinion
01The Setup

I write React for a living. I've shipped server components, debugged hydration mismatches at 2 AM, and spent more time inside node_modules than I'd like to admit. React pays my rent. I genuinely enjoy working with it.

So when I sat down to build this portfolio - the thing you're reading right now - I did what felt natural. I opened a terminal and typed <!DOCTYPE html>.

No create-next-app. No package.json. The whole site is hand-written HTML and CSS, with maybe 40 lines of JavaScript for the theme toggle and some scroll animations. I edit a file, push to Vercel, and it's live. That's the entire pipeline.

People find this funny. A frontend engineer's portfolio with no frontend framework. But after years of building SPAs, I think the funnier thing is how rarely we ask whether we actually need one.

02How We Got Here

At some point in the last decade, SPAs stopped being a technical choice and became the default. Junior devs spin up Next.js for a three-page portfolio. Agencies scaffold React apps for marketing sites. I've seen Gatsby projects for restaurant menus. A landing page that should be 4 KB of HTML ships as a 300 KB JavaScript bundle with a loading spinner.

Nobody decided this. It just happened. Bootcamps teach React. Tutorials start with npx create-react-app. Job postings list frameworks as requirements. So when someone needs to build anything for the web, they reach for what they know. And what they know is React.

The cost nobody talks about

Every SPA has a startup cost. Before the browser can show a single pixel of your content, it needs to download your JavaScript, parse it, compile it, and run it. On a MacBook Pro with gigabit Wi-Fi, you don't notice. On a mid-range phone on spotty mobile data - which describes most of the world's internet users - it's seconds of staring at a blank screen. For text and images.

HTML doesn't work that way. The browser renders it as it arrives. There's no parsing step, no compilation, no hydration. The content is the payload.

The upgrade treadmill

Run npx create-next-app right now and you get 300+ MB of node_modules before writing a single line of your own code. That's fine - until six months later when half those packages have security advisories and the framework has a new major version with breaking changes you didn't ask for.

I've watched teams burn entire sprints on Next.js 13-to-14 upgrades that gave their users nothing. The framework quietly becomes a second product you maintain alongside the one you're actually building. My portfolio has zero dependencies. It'll render the same in a browser today as it will in 2031. HTML doesn't ship breaking changes.

Problems you didn't have before

SPAs create entire categories of problems that plain websites don't have. Client-side routing means managing browser history and the back button. State management means context, reducers, or a store. Code splitting means loading states for every route. SSR means hydration mismatches that only reproduce in production and make you question your career choices.

I deal with all of this at work. It's worth it there. But when the thing you're building is a portfolio or a blog - content that doesn't change between requests - you've imported all that complexity to serve static text.

03This Site

I'm not making an abstract argument. You're looking at the result. Right-click, view source - it's all there. No bundled output, no minified blob. Just HTML I wrote by hand.

0
Dependencies
0
Build steps
~50KB
Total code size

The browser starts painting the moment the HTML arrives. No spinner, no white flash, no layout shift while React hydrates. The CSS sits in a <style> tag in the head - one fewer network request. Fonts are self-hosted with preload hints. Off-screen sections use content-visibility: auto so the browser doesn't bother rendering what you can't see yet. The only JavaScript is a small IIFE at the bottom: theme toggle, fade-in animations, that's it.

These aren't exotic techniques. You'd use the same tricks in a Next.js app. The difference is I didn't need a build pipeline, plugin system, or configuration file to get them. I just wrote the CSS.

SEO works because there's nothing to work around. Every page is real HTML with real content in the source. Crawlers see what users see. No SSR layer pretending to be a static page.

And here's the deployment config. The whole thing:

{
  "buildCommand": "",
  "outputDirectory": ".",
  "headers": [{
    "source": "/assets/(.*)",
    "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
  }]
}

Empty build command. Output directory is the repo root. Vercel serves the files with a one-year cache on assets. No CI to fail, no build cache to invalidate, no production-only bugs from aggressive tree shaking. There's nothing to go wrong because there's nothing to build.

The durability angle is one I didn't fully appreciate until recently. A React app I built in 2019 would need real work to run today - CRA is deprecated, React Router has gone through two major rewrites, half the deps would have advisories. An HTML file from 2019 renders identically in a 2026 browser. I've built on the only layer of the web stack that doesn't break.

04What I Gave Up

The nav, footer, theme toggle script, and the entire CSS design system are copy-pasted across every HTML file. When I updated the footer links last week, I updated them in 10 files. There's no component, no partial, no include. Just find-and-replace and some discipline.

For 10 pages, this is fine. Annoying sometimes, but fine. For 50 pages it'd be painful. For 200, it'd be stupid. Scale matters when you pick your tools.

There's also no contact form, no comments, no search. Nothing dynamic. Adding a new article means copying an HTML template and manually filling it in. No CMS, no markdown pipeline. If that sounds tedious - it is, a little. But it also means I understand every line of code on this site, which is more than I can say for most React projects I've worked on.

05When You Actually Need a Framework

At work, I build a SaaS platform with real-time collaboration, complex form flows, drag-and-drop interfaces, and 200+ shared components across multiple teams. React is absolutely the right tool for that. The reactive rendering model, the component composition, the ecosystem - it earns its complexity every day.

If your app has state that changes frequently based on user interaction. If users expect instant navigation and optimistic updates. If twenty developers need to build consistent UIs from a shared component library. If your content comes from APIs and changes hourly. Those are real SPA problems with real SPA solutions.

The mistake isn't using React for a SaaS dashboard. It's using React for a portfolio. It's reaching for the same tool regardless of the problem because it's the tool you know.

06One Question

Next time you start a project, before you npm init, ask yourself: does this thing have state?

Not "might it have state eventually." Not "would it be nice to have smooth page transitions." Does the core experience require managing data that changes in response to user actions across multiple views? If you're building a dashboard or an editor or a chat app - yes, obviously. Use a framework. That's what they're for.

If you're building a portfolio, a blog, a landing page, a docs site - you need HTML. Maybe some CSS. Maybe a little JavaScript. The browser already knows how to render these things fast. You don't need to ship 300 KB of runtime to help it.

We've gotten so deep into the framework era that choosing HTML feels like a statement. It shouldn't. It should just be the obvious choice for the 90% of websites that are, at their core, documents. We already have a technology for rendering documents on the web. We've had it since 1993. It still works.