The Post-Modern Web: StimulusReflex, or the Renaissance of Simplicity, Part 1

11 min read Original article ↗

Julian Rubisch

Press enter or click to view image in full size

Photo by Jesus Kiteque on Unsplash

The year 2020 will be remembered as the year when a pandemic struck the world, and sanity returned to web development. While these two observations bear no apparent connection, they actually have a causal relation. With the world in lockdown and many developers forced into remote work, technologies empowering small teams, and solo practitioners to deliver outsized results are in high demand.

This year also saw the impressively-marketed release of HEY, Basecamp’s ingenious take on an improved email workflow. With it came a promise of renewal of ”old-school” web technologies like HTML delivered “over-the-wire”. HEY also served as a preview for new versions of Turbolinks and StimulusJS, which focus on embellishing the server-rendered web page with smart behaviors instead of trying to boil the ocean.

To be clear, this approach isn’t new (it dates back to the original notion of AJAX in the early 2000s). More recently, it has attracted attention through proponents such as Phoenix LiveView, Laravel LiveWire — and in the context of Rails: StimulusReflex, which this article is about.

Since Single Page Applications (SPAs) claim the title of comprising the “modern” web in its entirety, let’s call this new movement the ”Post-Modern Web”, for lack of a better term. After all, in the tradition of postmodernism itself, we want to challenge the “total truth” that is professed to amount to SPAs and microservices as opposed to multi-page apps and monoliths.

Disclaimer: This article reflects my personal opinion and is going to stir emotions. Keep in mind though, that I don’t claim to have the monopoly on truth. Rather the opposite, I embrace all kinds of different takes on this subject and encourage discussion and diversity. After all, we all want to make the web better! This is not a StimulusReflex tutorial either, rather a philosophical reckoning of the state of web development.

With that said, let’s dissect this topic piece by piece and see what StimulusReflex has to offer.

1. The Fallacy of Serverless Architecture

Serverless became a buzzword in the late 2010s, with companies from Amazon and Google down to Cloudflare providing options for running stateless functions. The promise of modularity, payment prorated to the second, and — not to be forgotten — environmentally aware computing was compelling.

On top of that, the notion that frontend developers could “just” extrapolate their JavaScript knowledge and write a full-fledged web app using a couple of serverless functions and a few API calls (more generally known as the JAMStack) attracted legions of programmers.

However, a lot of complexity in these scenarios is hidden in the details of how your application is structured and the entailing fine web of dependencies:

  • Business logic and application state both have to be integrated in frontend code.
  • Any CRUD operations have to be coded as API calls (presumably via GraphQL).
  • Those APIs will often be provided by disparate services with their own set of change, uptime, maintenance etc. policies.
  • Search Engine Optimization will be a source of problems, and can only be resolved by applying (you guessed it) server side rendering.

Note that this often goes unnoticed or is accepted as having no alternative: headless CMS here, GraphQL store there, and webhooks to trigger notifications on top of it.

Here‘s how a typical application (one that I wrote!) might be structured:

Press enter or click to view image in full size

Ruby on Rails (as a stand-in for others such as Django or Laravel), on the other hand, has proven to be a highly productive environment for generations of web developers over the past 16 years. It comes with a well-vetted set of tools to codify (and simplify) exactly the front-backend contract, such as Turbolinks, Rails-UJS, and most recently StimulusJS. Importantly, as a developer, you control all dependencies yourself — a fact that cannot be stressed enough, especially for solo devs. DHH dubbed this the Majestic Monolith and while — like any concept — it is debatable in its essence, it leaves you a lot more confident than a serverless approach where you have to monitor your dependencies all the time.

What does StimulusReflex put on the table? Nothing short of assuming responsibility of rendering and replacing your HTML responding to user actions, leaving you the freedom to focus on a tighter application architecture.

Here are the cornerstones of what it adds to your Rails application:

  • Server-side management of application state with a mental model that is easy to grasp (and will feel like relief to people frustrated by rendering JSON payloads from GraphQL endpoints)
  • First class support for reactive UIs through morphdom
  • Reusable server logic without enforcing a particular application structure

Here’s an example of how you could use StimulusReflex to devise a robust, late-bound filtering logic that can be re-used for any resource (I’m assuming that you read at least the Quick Start Guide of the StimulusReflex docs).

Press enter or click to view image in full size

Concern-type Reflex Architecture

When the user selects a category from a drop-down, StimulusReflex calls the filter method of the Reflex class and makes use of the standard Rails session object to hold the chosen category. The controller action is now able to re-render the page, now showing only the filtered records. This new version of the DOM is sent to the browser where it is diffed against the active page, and changed to reflect the new state on the server. No state is stored on the client; indeed, you'd see the same thing if you hit refresh.

2. The Current State of Web Performance

In recent time we have seen an evangelization of Single Page Applications (SPAs) that went by almost unquestioned. Now there are lots of perfect use cases for this, but there are also alternatives that can be considered from a software design, as well as a performance perspective. Software design and organizational questions include code duplication between front- and backend, dividing your team in siloed specialists rather than generalists, and so on. This excellent article by Jorge Manrubia offers interesting insights into this relatively recent perceived schism.

Let’s focus on the performance part here. SPAs are perceived as fast, and in fact they are, after they are done parsing and executing the downloaded JavaScript bundle. The crucial metric here is called

Time to Interactive

To become interactive after the initial request, an SPA has to

1. Download the HTML page and all resources (including the JavaScript bundle),

2. Parse and execute the bundle,

3. Fetch some initial data, presumably JSON via an API call,

4. Parse and render this data in the DOM

Leaving network latency out of the equation, phase 2 is the bottleneck here. The effort to parse and execute a JavaScript bundle depends largely on its size, which is why most libraries, or their respective build steps, go to great lengths to optimize this stage (by tree shaking and chunking).

One of the more recent strategies to come around this is to render your HTML server-side (sic!) and deliver it together with the JavaScript bundle containing the SPA as an entry point. Now while I can see merits in the code sharing („isomorphic JavaScript“) that is possible in such a scenario, consider how a request is served now:

  1. On the server, query the database and/or API to fetch initial data
  2. On the server, render HTML and send it over to the client
  3. On the client, parse and display the HTML
  4. On the client, parse and execute the JavaScript bundle and load the SPA
  5. Now the SPA is ready to take over.

Call me narrow-minded, but this seems like reinventing PHP with a code-sharing approach between server and client, but without the benefits of a clear architecture (like MVC, for example). Generating HTML on the server and sending it over including some additional JavaScript is what we’ve been doing for decades. Have we finally come full circle?

Multi Page Applications

Let’s look for alternatives, then. Or maybe: let’s not. Because everything can actually stay as it has been concocted by Tim Berners-Lee in the 1990s, albeit with a few drop-in replacements. Let’s look at what problems SPAs actually try to solve, and how a Multi-Page Application could address that.

1. Page Transitions

Every traditional navigation on a web site issues a new HTTP request along with all the CSS and JS parsing and execution shenanigans over and over again. Transitions from page to page feel jittery and slow. So what do we do about it?

Turbolinks, which you might have heard about if you’re working in a Rails context, improves on this situation. It intercepts navigation events exchanging normal HTTP for XHR requests and swapping out the HTML body. (There’s other things happening behind the scenes, like building up a cache that can be used when navigating back and forth, but I won’t discuss that further).

So the alert reader will notice: Swapping out the body means leaving the HTML <head> intact, thus not necessitating a re-parsing of CSS and JavaScript. A huge speed-up which still requires sending over HTML instead of JSON, but often the difference in byte size over the wire (especially when brotli-compressed) is negligible.

Of course, now some standard events like DOMContentLoaded won’t fire, which in the past lead to problems with your occasional JQuery plugin and left many developers puzzled, ditching Turbolinks from the Gemfile. Fortunately, as frameworks like StimulusJS become more and more widely used, these concerns are a thing of the past.

2. Reactive, Component-Based UIs

Turbolinks is great, but SPAs give us the capability to surgically exchange only small parts of the DOM, when the app state changes. Basically this necessitates the use of a client-side store for your state (think Redux, Vuex, React Contexts etc.) and all the complicated strategies involved for synchronizing it to your app’s data layer (Redux-Saga etc.).

So the generally accepted opinion to this date is that you cannot have reactive, component-based UIs using a server side rendered approach.

Wrong.

The recently released StimulusReflex v3.3 introduces different “morph modes”, one of which—the selector morph—is perfect for this use case. You can easily swap out page components by simply applying a morph to a CSS selector of your choice. So you can check a checkbox in one section of your page and update another section in a a few milliseconds. Use good old server-side sessions for state persistence.

Expanding on the example above, here’s how that could look like:

How is that possible? Selector morphs bypass the entire Rails middleware+routing stack and let you directly render whatever string you want into a DOM element present on your page. Making use of ApplicationController.render gives you maximal reusability of your view templates, too.

3. Isolated Frontend Codebase

This is an almost political issue: The move towards SPAs that can be distributed as static websites and only need an arbitrary web space to live in, without any runtime requirements such as databases or app servers, not only convinced a lot of decision makers to migrate to JAMStack environments, but also attracted many aspiring developers.

Let me say clearly that building applications without the need to care about backend languages or keeping your page live in the hours after Reddit discovers you is an achievement of great merit. Today we have an army of talented people building great value on the web. But it is also a somewhat misleading simplification and source of confusion down the road.

Believe it or not, the passage of time attracts entropy, almost guaranteeing that APIs change, SaaS start-ups go bust, headless CMS’s suddenly adapt their pricing schemes etc. Deciding whether to pull in a dependency or roll your own implementation is one of the toughest calls there is in software engineering. With some years of experience, I tend to start with no, but your mileage may vary from project to project.

Building an entire programming model around dependencies you cannot control from my current perspective—been there, done that—seems ill-advised to say the least. If you don’t want to take my advice, maybe take it from Sandi Metz:

Depend on things that change less often than you do.

I developed a healthy habit of scrutinizing my dependencies against this policy once in a while.

Intermission

In Part 2 we’re going to look at how a Websocket-centric, Server-Side-Rendered approach translates to material and immaterial profits, such as value delivered, increase in productivity and developer happiness. Stay tuned!