{"id":"egkf2xokjr31sml","title":"The modern web is silently collapsing and we’re all still using its relics","slug":"the-modern-web-is-silently-collapsing-and-were-all-still-using-its-relics","summary":"A lot of the modern web is stacked on old assumptions and heavy habits. This piece looks at what should be rebuilt, simplified, or finally retired.","imageUrl":"https://briancrabtree.me/images/journal-the-modern-web-is-silently-collapsing-and-were-all-still-using-its-relics.webp","category":"Architecture","date":"2025-12-14","featured":false,"likes":42,"author":"Brian Crabtree","content":"<h2>Identifying the Relics of a Failed Philosophy</h2>\n\n<p>If you peel back the layers of abstraction in a typical enterprise React or Angular application, you start to see the cracks in the foundation. The primary relic of this era is the colossal JavaScript bundle. It seems we have forgotten that bandwidth is finite and that latency is a law of physics, not a suggestion. Fueled by increasingly complex applications and a complete lack of discipline regarding third-party libraries, bundle sizes have spiraled out of control. We lean on tools like Webpack, Rollup, and Turbo to tree-shake and minify our code, treating them like magic wands that absolve us of the sin of writing inefficient software. But these tools are struggling to compensate for the sheer volume of garbage we pour into the funnel.</p>\n\n<p>The bloat directly impacts Time To Interactive and First Input Delay, metrics that actually matter to the human being on the other side of the screen. We focus so much on the \"developer experience\" that we forget the user experience entirely. The initial perceived speed of a single-page application often masks a brutal download and parse cost that penalizes anyone not running the latest MacBook Pro on a gigabit fiber connection. When you ship three megabytes of JavaScript to render a blog post, you aren't building a modern application. You are failing at your job.</p>\n\n<p>Consider the cold start problem, which is perhaps the most egregious offender. Every byte downloaded is a byte that costs time and bandwidth, often wasted on code that the user does not immediately need or may never use. This penalizes first-time users and those on metered connections disproportionately. We talk about inclusivity and accessibility, yet we build digital gentrification where only those with high-end hardware can participate in the ecosystem we created. Furthermore, the browser has to do something with all that code. Modern browsers spend a significant amount of time parsing and compiling JavaScript, locking the main thread and delaying interactivity. This CPU-intensive operation is the hidden tax of large bundles, directly contributing to poor Core Web Vitals and that sluggish, janky feeling that pervades so many modern sites.</p>\n\n<h2>The False Promise of Client Side Hydration</h2>\n\n<p>When the industry finally realized that shipping an empty `body` tag and waiting for JavaScript to paint the UI was a terrible idea for SEO and perceived performance, we didn't go back to basics. We doubled down on complexity. We introduced Server-Side Rendering and Static Site Generation, but we paired them with a concept that I consider to be one of the most inefficient architectural patterns in history: hydration.</p>\n\n<p>The idea sounds clever on paper. You render the HTML on the server so the user sees something immediately, and then you \"hydrate\" it on the client side to make it interactive. In practice, this creates a scenario I call the \"Uncanny Valley\" of user interfaces. The user sees a button. The button looks ready. The user clicks the button. Nothing happens. Why? Because the main thread is currently choking on 500 milliseconds of hydration logic, re-executing the exact same templates and logic that the server just finished processing.</p>\n\n<p>We are essentially suffering from Double Work Syndrome. The server renders HTML, and then the client burns battery cycles to re-render or re-process the exact same UI logic to attach event listeners. It is an egregious waste of compute cycles. We are making the user's device do work that we already did on the server, all to support a component model that might be too heavy for the task at hand. This monopolization of the main thread prevents user input processing, leading to high Total Blocking Time scores. It creates a fragile state where the UI looks broken because it essentially is broken until the JavaScript catches up to reality.</p>\n\n<p>This also introduces a nightmare of hydration mismatches. If there is even a single byte of difference between what the server thinks the UI should look like and what the client computes—perhaps due to a locale difference, a random UUID generation, or a browser API discrepancy—the framework throws a fit. It might discard the server-rendered DOM entirely and rebuild it from scratch, causing a layout shift that ruins the visual stability of the page. We are spending hours debugging reconciliation issues that wouldn't exist if we simply sent HTML to the browser and let the browser do what it was designed to do.</p>\n\n<h2>The Complexity of dependency Management</h2>\n\n<p>Beyond the runtime performance issues, we have created a development environment that is fragile and dangerous. The dependency graph of a modern web project is a terrifying thing to behold. Transitive dependencies often pull in entire sub-ecosystems, leading to security vulnerabilities and the infamous versioning nightmares that keep DevOps engineers awake at night. We have normalized the idea that a \"Hello World\" application requires two hundred megabytes of node_modules.</p>\n\n<p>This complexity demands disproportionate engineering effort. Instead of building features that add value to the business, we spend our sprints managing configurations, upgrading major versions of build tools, and fighting with type definitions. We are servicing the infrastructure of the code rather than the code itself. The attack surface of these applications is massive, and the maintenance burden is unsustainable. We are borrowing time from the future to pay for the convenience of the present, and the interest rate is rising.</p>\n\n<h2>Returning to Sanity through Server First Architecture</h2>\n\n<p>The solution is not to invent a new framework that promises to fix the problems caused by the previous framework. The solution is to stop fighting the medium. The browser is an incredibly capable runtime for parsing HTML and executing CSS. It is highly optimized for these tasks. When we bypass the browser's native capabilities to reimplement navigation, form handling, and state management in JavaScript, we are betting against the house.</p>\n\n<p>We are seeing a resurgence of pragmatic, performance-first patterns that reject the SPA-by-default dogma. We need to advocate for a return to server-side first delivery. This does not mean we abandon JavaScript or interactivity. It means we use JavaScript strategically to augment the experience, rather than defining it. Technologies that allow us to stream HTML updates over the wire, replacing specific parts of the DOM without a full re-render, offer the interactivity of an SPA without the crushing weight of a fat bundle.</p>\n\n<p>It is time to prioritize long-term system health over resume-driven development. We need to build systems that are resilient, where the core functionality works even if the JavaScript fails or hangs. We need to respect the user's hardware and bandwidth. The web was built on a foundation of simplicity and resilience. It is time we cleared away the rubble of the last decade's over-engineering and remembered how to build things that actually last. For a related angle I keep coming back to, see <a href=\"/journal/static-prerender-shells-spa/\">Static Prerender Shells: SPAs That Paint Before JavaScript</a>.</p>","tags":["Next.js App Router","React Server Components (RSC)","Server-Side Rendering (SSR)","TypeScript Web Development","Tailwind CSS Framework"],"views":111}