solidjs / solid-start

SolidStart, the Solid app framework
https://start.solidjs.com
MIT License
4.94k stars 371 forks source link

Future Architecture: Hybrid Routing + Minimal Hydration #400

Open ryansolid opened 1 year ago

ryansolid commented 1 year ago

As many of you have seen we have begun experimenting with future architecture, but there is still a lot of work to be done both to validate our results and generalize the approach so it is easily adoptable by developers. This issue is being made to lay out the body of our work and keep track of our progress in these areas.

This is a substantial task for an OSS project and it is our hope to expand efforts in this area. Both through OSS contributions and programs like Solid Fellowships. We believe that through this effort we can improve the overall quality of the web both for end users and for developers. And in so we are looking for help. Your help.

This issue describes future work that is independent of any SolidStart 1.0 release and represents patterns that still leverage core SolidStart structures like its FileSystem routing, Data Functions, and Progressive Enhanced Forms.

Motivation

It is no secret the size of JavaScript on websites has been increasing at a steady state over the past decade. Driven by our desire for more interactivity, and frankly a web architecture that has mostly disregarded this cost. And it is a cost especially felt on slower networks and devices. The web is for everyone and this is largely unacceptable.

Single Page App architecture, the one most often used when building SolidJS applications, is one where the assumption is where all UI processing will eventually make it to the browser. What can be on the client will be on the client. Attempts to mitigate this have had some amount of success. Server-side rendering has improved the speed in which we display these applications. Smaller and performant frameworks like Preact, Solid, and Svelte have been able to deliver amazing experiences with a smaller footprint. But ultimately these do not scale any better. At a certain complexity of application, any wins by the framework get offset by the sheer amount of end-developer code. This is not those developers' fault but an architecture that doesn't allow them to achieve their goals.

Opportunity

Solid's novel approach to reactivity and reactive rendering has caused a bit of a renaissance in frontend the past years influencing projects like Vue Vapor, Preact Signals, Marko 6's Sub-Component Hydration and Resumability, Qwik's Finer-Grained Template-less hydration, and even new proposals for upcoming versions of Angular. The reason is it abandons the runtime Component model popularized by React and used in most frameworks for one that scales on interactivity rather than view size.

This approach to optimizing client rendering which has made Solid formidable in most benchmarks over the past 5 years, may also our salvation for solving the next set of problems regarding hydration and reducing our JavaScript footprint. I spent the past year exploring, talking with creators, and reverse engineering solutions across the ecosystem to understand the state of the art. I've worked with different framework authors benchmarking and raising awareness for the problem space. Netlify came on board to sponsor my efforts to improve relations and build a better web in OSS. I think it is vitally important to continue this work to realize this future architecture.

Overview

If things are to move more to the server it starts with the understanding we need to not have everything in the browser. The most redundant things are what we often refer to as the double data problem. The modern server-rendered JavaScript app sends the data both encoded in the final HTML output and as JSON data in a script tag so that it can hydrate. It also ships the "template" code both in the realized HTML and in the JavaScript bundle. These are important places we should be looking at reducing our overhead.

Converse to recent popular opinion this means reducing the surface area of isomorphic code. If most of the code only needs to be run on the server we eliminate both examples of "double data". Isomorphic code is still very important because depending on the server for all interactions is equally a recipe for poor site performance.

And performance is a big concern here. We've seen server-driven hybrid's in the past but many have been branded as feeling clunky. Too much reliance on servers for interactivity. Too much back-and-forth serialization to maintain app and view state. There have been versions of this dating back through things like ASP.NET UpdatePanels to things like Turbo. And in our opinion, we are yet to see a solution that fully succeeds at this marriage of user experience and developer experience goals.

We have newer metrics like INP(Interaction to Next Paint) that are giving us a better idea of the latency of our app experiences and we need to ensure that we aren't only improving TTI (Time to Interactive) and sacrificing other metrics.

Approach

We see that we need to address this through a few key areas that need to work all in tandem. If any area is not optimal enough it could undermine the viability of the whole effort.

Hybrid Routing

The focus of this work is leveraging the server more to save client-side costs. This revolves around rethinking the routing paradigm of modern frameworks. The router is the backbone of any web architecture, as the web itself is just a giant router. Every generational improvement in architecture has been accompanied by an evolution in routing. The early document web was based on file system routing. This evolved into the dynamic web which saw the advent of server routers to field requests and send back the appropriate resources. The Single Page App is defined by its client-side routing. History API enabled isomorphic server-rendered SPAs. And the next stage is no different.

The idea being presented is to serve future navigations with HTML partials. These partials should be able to be streamed in and represent only the parts of the page that need to change. The pattern we've been investigating so far is using Nested Routing (as popularized by Ember.js). We have prototyped a modified version of Solid's router that on the server only sends parts of the route that have changed, and a minimal client router that intercepts anchor clicks and is responsible for swapping these portions on navigation.

An additional consideration is "reload" or "refresh" actions where changes happen on the same route. In these cases state needs to be preserved. It is not enough to just replace the HTML. This requires some level of diffing against the existing page to ensure state preservation.

Hydration Reduction

If we wish to keep client routing we need to rethink all adjacent technologies. Things that had narrower scope previously return to having greater importance. Hydration for instance is now something felt on every navigation instead of only the initial load. This means that not only are we concerned with how to navigate and but how we establish interactivity in our application.

The easiest entry into this is using "Island" or manually defined (isomorphic)hydrated zones. With this approach, the code outside is assumed server-only and does not require being sent to the browser. This has the convenient side effect that only data that data that is passed directly to these Islands needs to be serialized, and any data fetched and used outside can be skipped.

This approach has been used at eBay for almost a decade and has been shown to yield savings of 60-80% on pages. Combining with Hybrid Routing does add a bit of overhead but the bigger concern is whether this experience is sufficiently smooth for future navigation after the page has already loaded.

Detailed Implementation

I've put together more detailed document describing implementation details here: https://hackmd.io/@0u1u3zEAQAO0iYWVAStEvw/rJFCoM4Di

Progress

As of Nov 4th 2022:

We have created early prototypes of Hybrid Routing and Islands.

Initial Routing Demo Hackernews Start Docs Movies App

Work Done So Far

Work Left to Do

lxsmnsyc commented 1 year ago

Just saw this, what does Closure reversing compiler mean?