https://www.jonoalderson.com/conjecture/its-time-for-modern-css-to-kill-the-spa/ [jono-alderson] Jono Alderson Independent technical SEO consultant [ ] Menu * Home * About * Speaking & Events * Consulting * Blog * Recommendations + Tech & Tools + Books * Contact It's time for modern CSS to kill the SPA 24th July, 2025 [32defd3f59a0] [file_00000000be4861f49d1f8af54cec64de] Native CSS transitions have quietly killed the strongest argument for client-side routing. Yet people keep building terrible apps instead of performant websites. The app-like fallacy "Make it feel like an app." At some point during the scoping process, someone says the words. A CMO. A digital lead. A brand manager. And with that single phrase, the architecture is locked in: it'll be an SPA. Probably React. Maybe Vue. Almost certainly deployed on Vercel or Netlify, bundled with a headless CMS and a GraphQL API for good measure. But the decision wasn't really about architecture. It wasn't even about performance, scalability, or content management. It was about interactions. About how the site would feel when you click around. The assumption was simple: Seamless navigation requires us to build an app. That assumption is now obsolete. The false promise of SPAs The reason SPAs became the default wasn't because they were better. It was because, for a while, they were the only way to deliver something that felt fluid - something that didn't flash white between pages or jank the scroll position. But here's the uncomfortable truth: most SPAs don't actually deliver the polish they promise. What you usually get is: * A page transition that looks smooth, until you realise it's just fading between two loading states * Broken scroll restoration * Inconsistent focus behaviour * Delayed navigation while scripts rehydrate components * Layout shift, content popping, or full-page skeletons * A performance hit that's entirely disproportionate to the effect This isn't theoretical. Look at most sites built with Next.js, Gatsby, or Nuxt. They're shipping kilobytes (often megabytes) of JavaScript just to fake native navigation. Routing logic, hydration code, loading spinners - all just to stitch together something that browsers already knew how to do natively. Instead of smoothness, you get simulation. And instead of a fast, stable, SEO-friendly experience, you get a heavy JavaScript machine trying to recreate the native behaviour we threw away. We've been adding mountains of JS to "feel" fast, while making everything slower. An aside - I went deeper on this in JavaScript broke the web, where I outlined how our obsession with JS-first development is actively eroding the web's foundations. The web grew up While we were busy reinventing navigation in JavaScript, the platform quietly solved the problem. Modern browsers - specifically Chromium-based ones like Chrome and Edge - now support native, declarative page transitions. With the View Transitions API, you can animate between two documents - including full page navigations - without needing a single line of JavaScript. Yes, really. What we're calling "modern CSS" here is shorthand for View Transitions, Speculation Rules, and a return to native browser features that were always designed to handle navigation, interaction, and layout. These capabilities let us build rich, seamless experiences - without rewriting the browser in JavaScript. An aside - CSS is also declarative, resilient, expressive, scalable, and increasingly intuitive. It's accessible to anyone who can write plain HTML. And that structural clarity reinforces everything I argued in Why semantic HTML still matters - that clean, meaningful markup is the bedrock of performance, maintainability, and machine readability. That means you can: * Fade between pages * Animate shared elements (e.g. thumbnails - product detail) * Maintain persistent elements like headers or navbars * Do it all with real URLs, real page loads, and no JS routing hacks Let's make this concrete. Basic cross-page fade transition With just a few lines of CSS, you can trigger smooth visual transitions between pages. On both the current and destination page, add: @view-transition { navigation: auto; } ::view-transition-old(root), ::view-transition-new(root) { animation: fade 0.3s ease both; } @keyframes fade { from { opacity: 0; } to { opacity: 1; } } That's it. The browser handles the transition - no client-side routing, no hydration, no loading spinners. Shared element transitions Want to animate a thumbnail image into its full-size product counterpart on the next page? No JavaScript needed - just assign the same view-transition-name to the element on both pages: On the product listing page: On the product detail page: The browser matches and animates the elements between navigations. You can animate position, scale, opacity, layout - all with CSS. But what if I need JS-driven transitions? You can manually trigger transitions inside a page too: document.startViewTransition(() => { document.body.classList.toggle('dark-mode'); }); Perfect for things like tab toggles or theme switches -- without needing a framework or hydration layer. Speculation rules: instant navigation without JS View Transitions make things smooth. But what about fast? That's where Speculation Rules come in. This lets the browser preload or prerender full pages based on user behaviour - like hovering or touching a link - before they click. The result? Navigation that's instant. No waiting. No loading. No spinners. [?][?] A Note of Caution Speculation Rules are a performance multiplier. On a lean site, they make things feel instant. But if your pages are slow, bloated, or JS-heavy, speculation just front-loads those costs. If your site is bloated, speculation will still speculate - and the user pays the price. That means wasted CPU, network bandwidth, and mobile battery - often for pages the user never even visits. Use them carefully. On a fast site, they're magic. On a slow one, they're a trap. Browsers want to help - if we let them Modern browsers are smarter than ever. They're constantly looking for ways to improve speed, responsiveness, and efficiency - but only if we let them. One of the clearest examples is the Back/Forward Cache (bfcache), which allows entire pages to be snapshotted and restored instantly when users navigate back or forward. It's effectively free performance - but only for pages that behave. That means no rogue JavaScript, no intercepted navigation, no lifecycle chaos. Just clean, declarative architecture. Just HTML and CSS. Unsurprisingly, this plays beautifully with a well-structured, multi-page site. But for most SPAs, it's a non-starter. The very design patterns that define them - hijacked routing, client-side rendering, complex state management - break the assumptions that bfcache relies on. This is a microcosm of a much bigger theme: browsers are evolving to reward simplicity and resilience. They're building for the kind of web we should have been embracing all along. And SPAs are increasingly the odd ones out. SPA vs MPA: a performance reality check Average Next.js marketing site + JS bundle: 1 - 3MB + TTI: ~3.5 - 5s (depending on hydration strategy) + Route transitions: simulated + SEO: complex, fragile + Scroll/anchor behaviour: unreliable Modern MPA + View Transitions + Speculation Rules + JS bundle: 0KB (optional enhancements only) + TTI: ~1s + Route transitions: real, native + SEO: trivial + Scroll/focus/history: browser-default and perfect Modern CSS doesn't just replace SPA behaviour - it outperforms it. Don't build a website like it's an app Most websites aren't apps. They don't need shared state. They don't need client-side routing. They don't need interactive components on every screen. But somewhere along the way, we stopped making the distinction. Now we're building ecommerce stores, documentation portals, marketing sites, and blogs using stacks designed for real-time collaborative UIs. It's madness. A homepage with six content blocks and a contact form doesn't need hydration, suspense boundaries, and a rendering strategy. It needs fast markup, clean URLs, and maybe - maybe - a bit of interactivity layered on top. And yet, on every project: 1. A stakeholder says, "make it feel like an app." 2. A dev team reaches for Next.js or Nuxt. 3. Routing goes client-side. 4. Performance falls off a cliff. 5. Now you need edge functions, streaming, ISR, loading strategies, and a debugging plan. 6. And somehow... it still feels slower than a regular link click and a CSS animation. This isn't about being anti-framework. It's about being intentional. Use React if you want. Use Tailwind, Vite, whatever. Just don't ship it all to the browser unless you need to. Build a site like a site. Use HTML. Use navigation. Use the platform. It's faster, simpler, and better for everyone. Build for the web we have SPAs were a clever solution to a temporary limitation. But that limitation no longer exists. We now have: * Native, declarative transitions between real pages * Instantaneous prerendered navigation via Speculation Rules * Graceful degradation * Clean markup, fast loads, and real URLs * A platform that wants to help - if we let it If you're still building your site as an SPA for the sake of "smoothness," you're solving a problem the browser already fixed - and you're paying for it in complexity, performance, and maintainability. Use modern server rendering. Use actual pages. Animate with CSS. Preload with intent. Ship less JavaScript. Build like it's 2025 - not like you're trapped in a 2018 demo of Gatsby. You'll end up with faster sites, happier users, and fewer regrets. guest Label [ ] [ ] Name* [ ] Email* [Post Comment] guest Label [ ] [ ] Name* [ ] Email* [Post Comment] 18 Comments Inline Feedbacks View all comments Bob Bob Jul 25, 2025 6:53 pm You almost sound like you're anticipating Datastar. Reply Andrew Andrew Jul 25, 2025 7:56 pm Just commenting in jest, but command-clicking the link to your "JavaScript broke the web (and called it progress)" article caused _this page_ to navigate alongside the standard "open in new tab" behavior. You may have broken the web. Reply Jono Alderson Jono Alderson Reply to Andrew Jul 26, 2025 10:12 am Thanks - fixed! Ironic stupid JavaScript bug Reply Andrew Andrew Jul 25, 2025 7:59 pm Sorry, that was mildly rude to leave only that prior comment without more context. In general -- I appreciate your advocacy for _just using the modern web because it's awesome_. Thanks! Reply Nicholas Nicholas Jul 25, 2025 8:54 pm This is amazing. I really love the idea of defaulting to native browser features until there's a real need for something more advanced. Frameworks are great for developer experience -- no doubt. But too often, they push you into shipping JS-heavy solutions that simulate what the browser already does natively. Are there frameworks that offer the same great DX, but lean into these new native capabilities -- things like View Transitions and Speculation Rules -- and handle it all at build time? And ready for production and heavy traffic? Reply Tom Boutell Tom Boutell Jul 25, 2025 11:43 pm I want to believe! But Firefox support isn't 100% yet: View Transition API - Web APIs | MDN https://share.google/ lHz2xma4MTlhMaTeQ Reply john Andrews john Andrews Jul 26, 2025 12:14 am Wow! An essay from Jono I actually like and agree with! Congrats! There's an old fable about a man going on a journey (to SomeWhere) and as he meets travelers he gets steered to side stops and cute towns and scenic routes.. The travel wears him out before he gets anywhere near SomeWhere, Every distraction was rationally justified... even with some saying that staying on the standard route was like wasting the trip.. missing all of the charm by staying on the sterile highways. Sometimes.... often?... the actual planned destination was the point of the entire journey. Without extreme stamina and extra resources, the divergent traveler ends up missing the whole purpose. I called Edge SEO "Wedge SEO" because of the way devs sidelined knowledgeable SEO people by driving a wedge between those hoping to pursue new goals (which then demanded Edge techniques as a means of SEO survival) and those chasing the original goals... which were business goals. Merit-based SEO chasing performance got sidelined for "modern" solutions that cost a lot more time and resources, with complexity, and yet... didn't often perform well. With complexity hidden in the edge cloud tech, further compounding problems. Reply Steve Steve Jul 26, 2025 12:30 am I'm listening!!! Well said. Recently I've been thinking of the web browser as an operating system with native primatives and some pretty sensable defaults. Reply Manoj Manoj Jul 26, 2025 4:24 am This was a fantastic read Jono - great article and very insightful indeed. Reply Casey Dwayne Casey Dwayne Jul 26, 2025 6:45 am I absolutely agree to a point. The amount of technical debt some projects accrue is insane. Interested to check out these things. I don't think we're out of the framework woodworks yet but with the advances in JS, CSS, and now native browser behavior I am optimistic we'll return to a cleaner, more sensible DX. Reply Stephen Reed Stephen Reed Jul 26, 2025 10:19 am What would be a best practice prompt for Claude Code to use this modern MPA design pattern? Reply Jono Alderson Jono Alderson Reply to Stephen Reed Jul 26, 2025 12:17 pm "use WordPress" Reply Caspar Harmer Caspar Harmer Reply to Stephen Reed Jul 26, 2025 8:33 pm Probably not many code examples scooped up by LLMs yet, so vibe coding might be out. Reply martrapp martrapp Jul 26, 2025 11:39 am Hi Jono, Motivating article, and definitely true for many websites. Thanks for spreading the word about how the View Transition API will change the way we build websites, and how users will tell old-school sites from modern ones in the future. I understand that you included the first code snippet to show how easy it is to enable cross-document view transitions and style the pseudo-elements. For an example likely to be copied by people new to the API, you might consider @view-transition { navigation: auto; } ::view-transition-group(*) { animation-duration: 0.3s; } Not only is it shorter, but more important, wouldn't override the API's default styling (inheritance, isolation) so carelessly. Reply Jono Alderson Jono Alderson Reply to martrapp Jul 26, 2025 12:18 pm Ah, great example! Thanks! Reply Sylvain Sylvain Jul 26, 2025 12:20 pm The article could also mention that SPA require more efforts in term of web accessibility, and I am not just talking about the scroll restoration. When a browser loads a new page, it will announce it to screen readers. With a SPA people will often need to implement a live region and an autofocus on the page h1. Reply Jono Alderson Jono Alderson Reply to Sylvain Jul 26, 2025 1:10 pm Absolutely! I touch on this in https://www.jonoalderson.com/ conjecture/why-semantic-html-still-matters/ and https:// www.jonoalderson.com/conjecture/ javascript-broke-the-web-and-called-it-progress/. Reply Brian Brian Jul 26, 2025 4:19 pm I remember when web development used to be enjoyable... and then SPAs reared their ugly heads, sucking the joy out of front-end work with their preposterously overwrought frameworks. I cannot wait to share this article with the next person who dares to utter, "We should use for our next CRUD project!" Reply I'm speaking at... SEOktoberfest / G50, 2025 2025-09-21 / 2025-09-26 Stanglwirt, Austria seoktoberfest.net Spotlight, 2025 2025-10-29 Amsterdam spotlightconf.com Recent blog posts... [20250725_152851-rotated] What the world's first factory can teach us about AI 25th July, 2025 [file_00000000aaf061faafa67952f692b] Why Semantic HTML Still Matters 21st July, 2025 Hire me! I'm an award-winning consultant, and I'm available for hire. Ready to take your website's technical SEO, performance, or structured data strategy to the next level? Hire me as a consultant Get in touch Email: me@jonoalderson.com * [twi]Visit my Twitter profile * [bsk]Visit my Bluesky profile * [lin]Visit my LinkedIn profile (c) Copyright 2025 Applied Energistics LTD. * Terms * Privacy * Cookies * Contact JonoAlderson.com is operated by Applied Energistics LTD - a company registered in England and Wales (Company # 11681212), at 38 Jute Road, York, YO26 5EN, United Kingdom [ ] Insert