This is a living document. Come back often to get updates.

JavaScript

Frameworks and rendering techniques

I mentioned this before, but since JavaScript frameworks and their ecosystems are very prone to building it, I want to remind you that the first rule to succeed at adopting a new technology is to avoid the hype. Refer to the homepage to evaluate if you need microfrontends, and to architecture and patterns to learn about best practices and patterns.

Remember that if an implementation is working for you and it solves yours (or your customer's) business problems, you do not need a migration or a new piece of tech to maintain.

It is a mistake to adopt a new architecture paradigm or set of principles, if they're not compatible with your solution.

Micro frontends and JavaScript frameworks

JavaScript frameworks come in all types and flavors, and can be categorized in multiple ways, depending on the criteria you choose. The most common criteria are:

Render strategy

  • Client-side (at runtime)
  • Server-side rendered (universal or isomorphic)
  • Static generation (at build time)

JavaScript boot up

  • Full hydration (full app bootstrap)
  • Partial hydration
  • Island architecture
  • Resumability

Application type (based on routing strategy)

  • Single Page App
  • Multi Page App

Application type (based on use of libraries, open standards and cloud technologies)

  • Static Web App
  • Progressive Web App
  • Functional Web App

Please note that these types are not mutually exclusive. They are just ways of classifying an app, depending on the predominant characteristics.

We are going to focus on frameworks that are considered new generation typically known as `meta-frameworks`, and that prioritize runtime performance via an html-first strategy, minimal or zero transpilation, the use of web platform APIs, and techniques to reduce JavaScript load and execution on the client-side, including progressive hydration, resumability and streaming.

This is why we're not going to be discussing client-side rendering, except to mention universal rendering techniques, that are used to render a client-side and a server side version of the HTML, and then transfer state from the server to the client, during the bootstrapping of the application.

I explain universal rendering with a diagram and the impact to some of the most important performance metrics at that time, in this slide deck from 2020.

Why the shift from client-side to SSG and/or SSR?

It feels like frontend development has been going in circles for a very long time. From concatenating all static assets to send them in huge files in one request, to bundling them and sending them in multiple requests. From splitting frontend code (typically HTML, JavaScript and CSS) into different files, to now inlining JavaScript ot CSS to the components above the foldm to combining technologies in one file (JSX, TSX, etc). And I could go on and on...

The most significative change of paradigm in the last 5 years, has been a resurge of moving the rendering of the HTML from the client, a strategy introduced by client-side frameworks promoting the development of Single Page Applications, back to the server...But why?

The answer is simple: it's all about performance. About user experience. About speed. And obviously, about avoiding end-user bounce and facilitate conversion.

Client-side rendering results in bad performance scores

The speed at which client-side rendering happens is affected by way too many variables, as I will explain later in the user experience section. Metrics like First Contentful Paint, Time to Interactive, and Speed Index, are affected by the amount of JavaScript that we request from the server and the way it is booted up -with the consequent latency factor weighin in- and how it is executed, that may be impacted by the device and browser capacity -or lackthereof- to run the code.

You can find more information on important metrics for good user-experience, visiting the Core Web Vitals page.

The birth of the meta-frameworks

With the general acceptance of the idea that client-side rendering is sub-optimal for performance, a new breed of frameworks emerged.

Meta-frameworks are a descriptive term for those frameworks that implement some of the optimizations described above, and are typically isomorphic, implement file-based routing, and serialize state to transfer it over the network during a process we will later describe in-depth, called re-hydration -also known as hydration.

What is isomorphism?

Iso means `equal` and `isomorphic` means something like evenly morphing or changing smoothly. When it comes to JavaScript frameworks, isomorphism is a term used to describe the universal capabilities of the framework that runs JavaScript both on the server-side and on the client-side.

What is serialization?

Serializing is the process of converting an object into a format that can be stored in a memory buffer, or sent over the wire using standard protocols. For example, when you convert a JavaScript object into a JSON string, you are serializing it.

Static Site Generation - aka SSG

Static Generators typically generate HTML from markdown, at buildtime.

The result are therefore static pages that are usually cached at CDN level.

Typically styles are inlined and sometimes JavaScript is bootstrapped, either totally or partially (hydration) to inject state, to enable routing or some dynamic features.

You can see the slide from the same deck, here.

Server-side Rendering - aka SSR

Frameworks supporting server-side rendering or universal rendering, typically generate a server-side and a client-side version of the render. The server-side version is the static HTML, oftentimes cached, and the client-side version is the result of bootstrapping the application.

State is transferred from the server-side to the client-side, during the bootstrapping or initialization of the application, with considerable overhead. This process is called hydration.

Hybrid rendering

Hybrid rendering strategies combine static generation with server-side rendering techniques and total or partial hydration.

HTML-first

All the afore mentioned rendering strategies require some sort of transpilation or compilation step.

HTML-First frameworks, however, promote an HTML plus web platform APIs strategy, following the progressive enhancement approach.

static site generation

image caption: static site generation flow - buildtime

server side rendering

image caption: server-side rendering flow - on the fly

Hydration

Hydration is the process of transferring serialized state and dependencies from the server to the static HTML in the client. The HTML can be generated with a static site generator at buildtime, on the fly -meaning because of an event took place on the client-side, that triggered a request for additional HTML- or was pre-rendered and sent from the server -or a caching layer-.

There are different methods to hydrate -or re-hydrate- static HTML: it can be fully hydrated, progressively hydrated or partially hydrated.

Full hydration

This is the most common method, used by most frameworks that support server-side rendering. When full hydration is implemented, the whole application is bootstrapped, typically during pageload, destroying and recreating the whole Document Object Model -DOM- tree, while state is evaluated and/or data is transferred.

Progressive hydration

Progressive hydration proposes to bootstrap parts of the tree independently, or progressively over time. This technique helps reduce the size in bytes of the JavaScript required to initialize an application, and also helps reduce the time to interactive for dynamic components.

Partial hydration

Partial hydration is the concept behind the Island Architecture approach, pushing the idea of progressive hydration even further. It suggests that we define areas of interactivity of our horizontal split, that may or may not need JavaScript bootstrapped to them, and it should be the page and not an additional shell, that transfers state to each one of those.

hydration

image caption: JavaScript hydration

Streaming SSR

Streaming is a technique through which the server sends -read streams- chunks of data sequentially, -individual snippets of HTML- to the frontend, to render over time.

Because we can decide what chunks we want to stream earlier, typically those that will be rendered over the fold, we can assume that we will get better Lighthouse and Web Core Vitalsscores.

This technique is also implemented in combination with edge computing or edge functions for composition, at the edge of the network, to return a full page back to the CDN for caching and to the client-side to render the whole view. With Node.js as a runtime, we can leverage its Stream API, to implement this technique.

Future versions of this site will include links to code samples, to illustrate this approach.

data streaming

image caption: SSR streaming

Node.js Stream and Web Streams API

Although Node.js streams can be converted to Web Streams API, to stream data from the server to the client -not in the oppossite direction, though-, both APIs are not the same API.

Learn more in the Web APIssection.

Island architecture

As briefly mentioned, `Island architecture` is an implementation of partial hydration that uses the analogy of isolated regions of a page or view, that could be either static or dynamic.

We can assume that the whole view will be server-side rendered and that for those micro-parts that are stateful or dynamic, hydration, or loading and initializing the necessary JavaScript for that part of the view to become dyamic, will occur in isolation.

One of the characteristics of Island Architecture that makes it a special form of partial hydration, is that there is no need for a shell. Each micro part is effectively independent from the others. It is basically the page itself that identifies the placeholders for those islands that need to download JavaScript and bootstrap it, and handles the process.

No matter what type of hydration we're talking about, a not so obvious downside when we're not familiar with the internals of the mechanics is duplication. When we hydrate a page, we are literally re-rendering it. So to hydrate we are duplicating the render -meaning the DOM tree-, the code and even sometimes the data and the state, with a consequent degradation of performance or even a glitch, where data on the client-side updates in way that is visible for the user. Even when partial hydration is a lot less impactful to performance than a full client-side boot up, it can still degrade it. And it definitely will have a certain cost of execution, even when minimal.

island architecture

image caption: island architecture

Resumability

This is where the concept of resumability or zero cost of execution, comes into play as a potential viable solution. Instead of loading and executing JavaScript on the client-side, to enable dynamic features, the server sends the render and a serialization of both state and handlers, so basically the client can pick up or resume exactly where the server left.

What does `pick up where the server left` exactly mean?

It means that because those assets are sent in a serialized form -meaning, as already explained, converted to a format that can be transferred over the wire-, the client can resume the execution of the code, and the state of the application, without having to re-render the whole view.

More on Resumability

These are some of the frameworks or libraries, implementing or exploring resumability:

Qwik and Solid.js.

More on Island Architecture

These are some of the frameworks or libraries, implementing or exploring resumability:

These are some of the frameworks or libraries, implementing or exploring Island Architecture:

Astro, 11ty, Marko and Fresh

Other frameworks

Other frameworks worth checking are: Nuxt, SvelteKit and Next.js.

`Wait a minute! You didn't mention my favorite framework! Does it mean you don't recommend checking it out?`

Short answer: NO! There are dozens of frameworks! Use the one you like best! They don't fit all on this site!

I'm not focusing on the many amazing frameworks that do CSR or SSG, only. Some of them implement universal features, and are evolving towards a hybrid and mode modular, approach, but that I will discuss in future updates.

I'm also not specifically mentioning frameworks for mobile development, because they're out of the scope of this site, at least for now. But if you're interested in learning more about mobile development, I recommend you head to Flutter, NativeScript or Ionic, to learn more.

Please note that supporting a pattern is not mutually exclusive with supporting another. Frameworks are well known for promoting a certain architecture, to be implemented without a lot of effort -given they are designed with a rendering model in mind-, but you can potentially accomplish a lot more by experimenting. Also modern frameworks tend to be actively developed, and add more features over time.

Microfrontends and web standards: Web Components

Web Components are actually a set of three different standards that allow developers to create reusable components, in an object oriented way. These three main technologies are:

  • Custom Elements, a collection of web platform APIs that can be laveraged to create a custom HTML element and define its behavior.
  • The Shadow DOM, another set of native APIs that enables a shadow or virtual object model that is attached to the custom element, and encapsulated away from the main document object model functionality.
  • And finally, HTML templates, a capability to write markup that is not immediately rendered in the page, and can be used as a blueprint to any custom element.

Go here to learn more about Web Components.

Because Web Components are basically part of the Web Platform native APIs, using them reduces the amount of code we need to write, compile and ship to the browser to make an application work, while enabling interoperability with other APIs available to the browser.

Because Web Components implement JavaScript class, we may need to consider a framework to help us create and instantiate them, as well as add reactive features and manage state.

Lit is a lightweight library providing all necessary modules to create and instantiate Web Components, in a declarative way. I personally love its simplicity.

Web Platform APIs and Micro Frontends

I have already mentioned the benefits of sticking to browser native APIs, and Web Components are not the only set of API's that are a real good fit to composable frontends.

When we use an HTML-first approach, we want to leverage native HTML elements, and only use additional JavaScript to enhance them, when strictly necessary. As you can imagine, custom elements -meaning, elements we come up with to fulfill a feature requirement- are the perfect way to accomplish this goal, also known as progressive enhancement. We can even wrap native HTML elements in custom elements, to extend their functionality, and we can also use the Shadow DOM to encapsulate any additional capabilities as a product of the enhancement, so it doesn't interfere with the native element or other native elements in the page.

More on Progressive Enhancement

I remember the days when I started doing web development. Back then progressive enhancement and graceful degradation were concepts and techniques used when we had to support enterprise sites with JavaScript disabled -yes! they had to provide almost identical functionality as with JavaScript enabled, as a requirement...Just imagine!- We also had to support cross-browser compatibility when the engines were a lot less mature and the standards were not as evenly implemented as they are today, across many of them in our support matrix.

So we either implemented graceful degradation, meaning we implemented all the features and then we removed -or blocked somehow the execution with conditionals- of some of the functionality where unsupported, trying to preserve the user experience as much as possible, or progressive enhancement, where we started with the bare minimum to ensure requirements were met, and added enhancements progressively where supported.

We implemented tons of device and agent detection for that -blocking the critical path- ouch!

Today, I feel there is a lot less of that, but I still believe progressive enhancement is a good practice to preserve the user experience, and when it comes to choosing the framework or technology stack for our frontend, thinking of an HTML-first approach, is a good way to go.

Microfrontends and events: the Streams API

The Web Streams API is a standard Web Platform API, that can access a response body sent over the network via the HTTP protocol, when it has been exposed as a readable stream, so a developer can lock a reader. That would be a readable stream, but the API allows us to create writeable streams, as well.

The interesting aspect about this API, is that it simplifies the process of consuming raw data on the client-side, directly from the network, without having to implement a buffer.

This article by Thomas Steiner can help you understand streams in the browser more in-depth, and has information about browser support for the time the article was written.

Other browser APIs

As I described in this article, two years ago, there are quite a few browser native APIs that can helps us compose beatuful and consistent user interfaces, without overhead, and that can be laveraged by independent teams working in decouple frontends.

I will elaborate more on each in future iterations or when I start release code samples.

  • Mutation Observer
  • Intersection Observer
  • History -especially important to build a custom router and the URL strategy for UX-
  • Channel Messaging
  • Web Workers
  • Web Sockets
  • Drag and Drop
  • Picture-in-Picture
  • Push API

Service Workers

Service Workers are a great capability to implement progressive enhancement, out of the box and from the get go: if a user visits a site implementing service workers with a browser that doesn't support them, no functionality will be be lost, and nothing will break.

But what exactly are they and how do they work?

Service workers are a complex piece of technology and I can't condense all they do in a simple paragraph. The idea is to extend this site with practical examples and even challenges to get started with technologies, but for now, let's just say that service workers are a technology that runs in its own thread*, has its own lifecycle, and it's built to intercept network requests and responses, and cache them, so they can be served offline, or even when the network is slow or the connection goes intermitently offline and online.

Service workers are a great aid to implement e2e event-driven architectures, and particularly helpful is its implementation of the Cache interface that, unlike the HTTP caching mechanisms that are header based, can be programmatically controlled and has access to the worker's scope running in its own thread, but also has access to the main thread of execution, so it can be used to cache any kind of data not only network responses. This is also useful to compose micro frontends from cached data, since we can either precache -cache on install- or implement cache on demand, at runtime, caching data from network responses from other sources, or from browser storage, like IndexDB or localStorage.

*If you're somewhat new to JavaScript, or to programming in general, you may not know this, but JavaScript runs by design in a single thread, and typically the only way to control execution is to understand very well the event loop and the call stack.

Trisomorphic Rendering

This takes us to another type of rendering, that I left out of the previous hydration section because it involves service workers, and I needed to explain them first. If isomorphic describes an implementation that runs both client-side and server-side, trisomorphic as you may have deduced by now, involves a third dimension and that is the service worker thread. We can use the service worker's caching interface to store full chunks of dynamic HTML to be integrated when the user demands a specific view, or in response to an event or change of state. Basically, we can serve a specific content from the service cache, when a condition is met.

Enhance.dev

One framework that is entirely designed around the concept of HTML-First, custom elements and pogressive enhancement, that I really like, is enhance.dev. This framework is very interesting because out of the box, it needs 0 compilation. It is JUST HTML -custom- elements.

As I explain before when I elaborate on what progressive enhancement is, you can progressively add JavaScript when strictly required, to enhance certain elements and make them more reactive or dynamic.

Enhance.dev also promotes the Functional Web App approach via the Begin CLI, a tool designed by the same creators, to deploy pure functions to a serverless context, and that's powered by an infra as code mechanism.

I will provide extended information on the concept of PWA -Progressive Web App- and FWA -Functional Web App- approaches, in future iterations of this site.

Web Workers

If you come from a pure frontend development background, or were recently trained in programming JavaScript that executes exclusively in the browser, you will need a complete mindshift to develop JavaScript that will be executed server-side. The APIs that are available in the browser, are not available in the server.

Not only you will obviously need a server to run your code, but you will need to strategically design your app so whatever is rendered in the server as static HTML, becomes later dynamic using the hydration techinques described above. Some features, like analytics or user tracking, involving cookies, etc, will definitely need to be run client-side.

Web Workers are of great help to run specific code in the browser, particularly code that needs to load and run early and has the potential of blocking the main thread, without actually blocking the main thread. Learn more about Web Workers here.

usecases

image caption: workers

Can I use?

The Web Platform standards and APIs are continously being developed and integrated to browser engines. Browser support for standards is more consolidated now that it was a decade ago, and still, some engines lag behind, while others take the lead, depending on the capability.

caniuse.com has been a reliable and up to date source of support information for a very long time.