Web development comes full circle with HTML-first front-end frameworks
In the beginning was HTML, and our pages were fast. But they were boring, so we added JavaScript.
JavaScript ended up being so capable that we ended up creating entire applications with it, dynamically creating and destroying DOM interactively to users’ delight. Applications grew in complexity and so frameworks were created to tame that complexity, giving developers components, state management, routing, and so much more. The developer experience with frameworks proved popular enough that websites that didn’t need a framework for the end-user experience ended up being created with frameworks simply for the development and maintenance benefits provided by the framework. Unfortunately, the end-user experience is often degraded by delays, layout shifts, and unresponsive UIs. Frameworks have been feverishly working to undo these harms, but the solutions are complicated and often unsatisfying.
There has been a movement to return to simpler days: simply providing HTML to the browser for websites that are not highly interactive and favoring server-generated HTML over client-rendered DOM generated from executing JS. The newer generation of server-side rendering frameworks allow developers to work exclusively with front-end web technology (JS/HTML, as opposed to Ruby, Java, and PHP). Static site generators (SSG) and server-side rendering (SSR) have provided tremendous performance benefits for websites, so web application frameworks are striving to achieve similar performance gains without losing the interactivity of JS-heavy single-page applications. An HTML-first front-end framework gives the highest priority to sending functional and complete HTML to the client while also providing a development architecture and experience similar to popular JS frameworks like React and Angular.
New applications of an old technique for fast web pages
The major front-end frameworks have been hard at work optimizing page load times and time-to-interactive for years now, but the results are still lacking. Some recent efforts to optimize client-side performance require new approaches in code structure and even code compilation.
One popular and effective approach has been described as “islands“, wherein portions of the page are completely static HTML as delivered by the server, and portions of the page are self-contained interactive apps that could be developed in an existing framework like React or Vue. Each island can independently be loaded on demand. Astro, 11ty, and Fresh use islands.
Progressive enhancement, which is providing a working page with HTML from the server and enhancing functionality client-side with JavaScript, is an old and effective technique that somewhat fell out of favor as JS-centric frameworks took center stage, but the Enhance framework uses this approach without sacrificing developer experience.
Going back to the question of “how do we create applications that utilize a lot of JS but optimize the delivery and execution of JS”, Marko and Qwik are aggressively pursuing this approach.
Marko is an OpenJS Foundation project that started at eBay. Its core members include Ryan Carniato, the creator of SolidJS. Module bundlers like webpack require careful configuration and tuning to split a page’s JS code into multiple bundles that can be lazy loaded to improve startup performance. Marko’s compiler analyzes the application code to minimize bundle size and deliver them on demand. Qwik follows a similar path; in its own words:
“Qwik is what you end up with when you take the idea of delay loading of JavaScript to the extreme.“
The Qwik API provides wrapper functions for use in components that allow the Qwik compiler to break up an application into very small chunks, often as small as a single function. This allows Qwik to bootstrap with a very small amount of framework code and from there use on-demand loading and smart preloading to bring the application to life. One unique optimization Qwik provides is resumable state. Other frameworks typically require a re-hydration process when used with server-side rendering to synchronize the JS application state with the HTML/DOM state. Qwik serializes application state on the server for extremely fast resumability on the client.
The speed of a sprinkling of jQuery
While browsers have been constantly evolving and optimizing both HTML parsing and JavaScript parsing and execution the fact remains that adding behavior to a page with JavaScript increases both network payload and execution time. Web application developers have been struggling for years to provide the most robust functionality with the least code, all while balancing those goals with the necessity to have a well-architected, testable, and maintainable codebase. HTML-first frameworks provide the best page performance possible by intelligently minimizing the amount of code delivered and executed. An important part of achieving this goal is minimizing the burden on developers of micro-managing exactly how it happens.
…with the developer experience of a modern framework
Web applications exist in a challenging environment: static websites are able to load quickly because they simply provide content and layout. Users have become accustomed to having a website load and be in working order very quickly. Web applications, although vastly different from static websites, often face the same end-user expectations.
Front-end developers have become accustomed to working with frameworks that provide the features that enable the creation of modular, testable, and maintainable code using standard web technologies. Modern HTML-first frameworks preserve these benefits while also providing the excellent performance of carefully hand-coded progressively enhanced web pages.
Building rich web applications with an HTML-first approach
Marko is in use at eBay which is a feature-rich interactive website. Qwik is used for builder.io which is a complex, interactive, stateful web application. For any public-facing website, performance should be a top priority. If you aren’t achieving high performance with your current system you should evaluate some other approaches to see if something better fits your needs.
For more complicated web applications and intranet applications, performance is still a concern. If application profiling and user studies show initial page load and responsiveness are significant issues then it’s definitely worth exploring new options.
Qwik currently seems to be at the forefront of maximally optimized JS delivery. Marko takes a similar approach and is keeping a close eye on Qwik. Marko 6 is in the works and it will be interesting to see what they achieve. Fresh is Deno’s framework and is developed in TypeScript, providing first-class TypeScript support for your development.
There are many HTML-first frameworks that meet a variety of needs in terms of technologies and approaches, allowing you to leverage existing skills and deployment environments. Although there is some support for common frameworks and technologies like JSX, it is important to understand that these frameworks often achieve performance improvements by putting restrictions on how code is structured and written. Reading a framework’s documentation and experimenting with a simple app will give you a better sense of how changes to state management and application composition might affect your projects.
Taking delayed loading to the extreme
We already know the fastest web page is a zero-JS webpage. So how do we create the fastest web application, which depends heavily on JS? Qwik has been examining this question from every angle and uses a number of optimizations:
- Deconstruct application source into very small independently loadable chunks. The real magic here is maintaining references to dependencies and restoring state.
- On-demand loading: only code that is actually executed in response to user actions is loaded.
- Partytown: this is a Builder.io library that enables the execution of 3rd-party scripts off the main thread in service workers. Even static web pages take a performance hit when loading analytics libraries.
Qwik’s loader is less than 1KB. All application event listeners delegate to global handlers (and global handlers are only registered for events the application code actually uses). The optimizer does significant transforms of your application’s source, inlining information in HTML attributes & comments, as well as JSON in script elements, so that the application structure and state are represented in the DOM.
This is counter to the typical separation between DOM and application representation. Typically the DOM is generally just the visual representation of the application while the logical structure and relationships are maintained in the application’s JS code. Embedding application structure and state in the DOM allows the application to resume execution with a minimal amount of JS loaded.
What should I use today?
While Qwik’s effectiveness is demonstrated by Builder.io, it is still a beta release. Qwik does build on existing web standards and web development approaches, but its optimization techniques do require new ways of thinking while building an application and put new constraints on your code. Qwik may be a good choice if performance is top priority for you, you appreciate Qwik’s approach, and you are willing to work with the Qwik community when encountering bugs and insufficient documentation.
If your application doesn’t require Qwik’s level of fine-grained on-demand loading, your choice of framework can be directed by what kind of templating/components you want to use, how important TypeScript is to you, and if you’re looking for an SSR deployment or SSG.
Qwik | Marko | Astro | 11ty | Fresh | Enhance | |
Architecture | Code splitting, resumable | Code splitting | Islands | Islands | Islands | Progressive enhancement |
Templating | JSX | Marko | Astro, .md, .mdx, .html | Many (HTML, Markdown, Web Components, …) | JSX | HTML custom elements |
Components | Qwik | Marko | Astro, React, Preact, Svelte, Vue, SolidJS, AlpineJS, Lit | Any | Preact | Web Components |
TypeScript | First-class | N | Supported | With effort | First-class | Supported |
Output | SSR, SSG | SSR | SSR, SSG | SSG | SSR | SSR, SSG |