The Problem React Doesn’t Solve
React has been a massive productivity boost for web developers since its release. Its declarative component model, built around rendering UI from state, fundamentally changed how we approach frontend development. But React’s own tagline—"A JavaScript library for building user interfaces"—is also a confession of its limits. React handles local component state well, but the vast majority of state in a typical web application isn’t local at all. It’s a cache of data that originated on a server, usually in a database.
Managing that cache is where things get hard. As the famous adage goes, there are only two hard things in computer science: cache invalidation and naming things. React has never offered a built-in answer for cache invalidation, which is why an entire ecosystem of libraries—Redux, MobX, Apollo, React Query, SWR—has grown up around the problem. These tools all exist to bridge a fundamental gap in web development that React alone cannot handle.
Crossing the Network Chasm
That gap is what we might call the network chasm—the divide between client and server code that every web application must cross.
Web developers write code that runs in two places: the browser and the server. The network between them is out of our control. When a React component re-renders, it needs synchronous access to data that lives on the server. So we make HTTP requests, store the results in an in-memory cache via React state or a library, and hope the data is there when we need it next.
The code required to shoot that "grappling hook" across the chasm all lives on the frontend. We want to co-locate data fetching with the components that need the data—that reduces bugs and overfetching—but that forces us to wait until components render before we even know what to fetch. Add code-splitting to the mix, and you have to load the code that fetches the data before you can fetch the data the code needs. The result is network waterfalls, a well-known performance killer.
Even React Suspense for data fetching won't fully solve this. Suspense is great at triggering fetches from within components, but if you want to avoid the waterfall, you have to start fetching before the components that need the data even render.
Fetching Sooner With Routes
This is where React Router is stepping up. By bringing Remix concepts like layout nested routes, loaders for data fetching, and actions for mutations into React Router, the framework decouples data fetching from components while preserving most of the benefits of colocation. The fetching code sits right next to the component that uses it, but it’s no longer trapped inside the render cycle.
With this architecture, the app no longer has to render to determine its data requirements—it can derive them from the URL. React Router manages loading and error states for you, handles cache revalidation, and tackles form resubmissions and race conditions, which are among the trickiest problems in UI development. Optimistic UI patterns become much easier to build. From the developer’s perspective, the network chasm narrows considerably.
Moving Code to the Server
Even with React Router managing data fetching in the browser, users still wait for JavaScript bundles to download and execute before seeing anything. And if the fetching code must load before the components render, that means the fetching code can't be code-split effectively.
What if all that code simply lived on the server instead? That would eliminate the pain of writing serverless functions every time you need to touch a database or hit a private API. React Server Components promise this for data loading, but they don't address mutations, and they're not available everywhere yet.
Remix closes the loop. With Remix, you take the data fetching and mutation code and move it into conventional route modules as exported functions. Those functions—loaders and actions—run exclusively on the server. The browser never has to download them, and Remix handles the entire network chasm for you.
The payoff is real performance: users no longer wait for JavaScript to see your app. The HTML arrives ready to use, and thanks to progressive enhancement, links and forms work even while the JavaScript downloads in the background. Because loaders and actions run server-side, they can talk directly to databases and services that use private keys—no serverless function needed.
Your Entire App, If You Want
Remix gives you enough server-side power to handle a full application on its own. Your app could look like this:
That said, you're not locked into going all-in. If you have an existing backend, Remix still manages the network chasm for you. The benefits apply whether you use 100% Remix or mix it with your current stack.
The Best of Both Worlds
React never promised to manage the network chasm—it just does a stellar job at rendering user interfaces. But every web application needs both. With Remix handling data fetching, mutations, and cache invalidation, you get a complete stack: a great rendering library paired with a framework that manages the hardest parts of full-stack web development. The result is simpler code, fewer bugs, and faster applications.



