Embracing Client-rendered Web Applications
The web has changed dramatically over the past 25 years. When I first started building websites, “under construction” gifs blinked in unison and Flash animations were still just experiments. Webpages then were mostly text-based content, structured in a format called HTML.
People styled their documents with CSS to improve readability or to add their own distinctive flare. They celebrated a separation of concerns between content and presentation, and I coded in awe of the original CSS Zen Garden.
They added JS to enhance the behavior and usability of their webpages. They could provide interactive date pickers, inform users of validation errors in forms, or render beautiful charts from source data tables.
I trusted these separations, and fell in love with the concept of progressive enhancement, which still aligns superbly well with text-based content.
Fast-forward 25 years, and while my appreciation for text-based content remains, I now find myself using and building primarily data-based web applications. For these projects, the separation of concerns between HTML, CSS and JS can often feel orthogonal to the concerns of the application itself.
First, modern web applications demand a level of fluidity that matches their native counterparts. And so while the first page load may work well in a progressively-enhanced design, subsequent asynchronous calls now require their own implementation: the server renders a more minimal JSON payload rather than the full HTML layout; and the client must be able to understand both the HTML and the JSON versions, as well as render the latter dynamically.
There are technologies like Turbolinks that aim to provide such a fluid experience without requiring a secondary rendering method, but in my experience that technique works best to add additional fluidity to webpages that already fit a progressively-enhanced design.
Second, modern web application content greatly diverges from the browser-displayed HTML, which is more strongly tied to the design’s structure and chrome than it is to the content. Navigation, tables, forms, modals, alerts and more become recurring components that house dynamic data. In these applications it can actually be more efficient to deliver the entirety of the site design and behavior on first page load, and then rely purely on asynchronous calls to obtain the specific data necessary to populate those components.
This can simplify your infrastructure, too. Single page applications can be delivered via a content delivery network (CDN) for increased speed and reduced cost and complexity. The index page can be kept quite small and show only an inline-styled loader or noscript content. Its CSS and JS assets can be named with a hash of their contents to enable long-term static caching.
Likewise, the server infrastructure can also be simplified. Free from UI concerns, it can focus its responsibility uniform application-based endpoints, which can be easier to version, document and maintain. This is also an excellent opportunity to expose these endpoints as your public API themselves, which can also improve and simplify application observability.
Efficiency could also be a concern – no one wants to wait for 5mb of client assets to download – but depending on your user behavior, it may be more efficient to deliver your application’s design and behavior on first page load. While this increases the cost of the first page load, it makes subsequent navigation lightning quick, as the majority of the document structure does not need to change, and old data can simply be swapped out for new data. Regardless, engineers have a responsibility to design efficient assets, and that is true regardless of the delivery technique.