Why Your Next Web App Frontend Might be The Backend

How LiveView and other real-time server-side rendered HTML technologies are changing how the frontend is written

Ouroboros — the head eating the tail (technically the frontend eating the backend, but you get the idea)

If you’re developing a rich, highly interactive web app in 2022, chances are you’re using some sort of frontend JavaScript framework such as React, Angular or Vue, which talks to a backend server via some sort of API, maybe based on REST or GraphQL.

While this JavaScript-heavy approach is powerful, it comes with some downsides. Recently, a different approach to building these kinds of apps is gaining popularity and it brings some interesting benefits. The new approach drives the frontend entirely from a backend server, and doesn’t involve writing a single line of JavaScript. In this article, we’ll explore how this new technique works, and consider some of the tradeoffs against the more common single page app (SPA) approach.

A brief history of Web architecture

Throughout the history of computing, even before the web, the pendulum has swung back and forth between server-oriented and client-oriented approaches.

DEC VT100 — an early “server-side rendering” client?

Let’s go back through some of the history of web architecture to see where we started, and where we are now.

Server-rendered, mostly static HTML

Once upon a time, web pages were written in pure HTML and served up directly from the backend. Once delivered, the pages did not meaningfully update on the client side. There were perhaps small JavaScript UI flourishes, but generally speaking, navigating through the app involved round trips to the server to fetch new, static HTML pages.

Ruby on Rails helped usher in the age of more dynamic web apps

Languages like PHP and frameworks like Ruby on Rails (and many others), helped to expand the capabilities of web apps by generating HTML dynamically for each request. While this approach greatly enhanced what web applications could do, the content was still, for the most page, static once it arrived to the browser.

JavaScript & rich, real-time single page apps (SPAs)

Sometime in the early 2000s, web pages started getting a little more… dynamic. New JavaScript tools (e.g., jQuery for DOM manipulation) & techniques (e.g. AJAX for fetching data from the server) cropped up which enabled pages to update dynamically in the browser after the initial HTML load. These updates were small at first, perhaps switching out tabs of content, or showing simple user interface feedback, but over time, JavaScript took on more and more of the rendering responsibility.

React — one of the champions of the single page app world

Before long, some apps eliminated the server-rendered HTML entirely, relying on JavaScript to do all the heavy lifting of rendering the page (usually with some HTML templates embedded in the JS). This approach evolved into the modern single page application we know today, driven by popular frameworks like React, Ember, Angular and many others.

Server-side rendering & Isomorphic rendering (SSR)

Though SPAs excel in interactivity and unlock the ability to build new kinds of applications, they do come with some downsides with regard to rendering time, frontend performance & SEO. Rendering an SPA often requires many round-trips to the server and because content is rendered with JS on the client, the process is typically much slower than static HTML. Furthermore, content is not readily available to some search engines (more on this below).

To address this, some frameworks began to introduce isomorphic rendering: the strategy of rendering an initial version of a page on the server, then letting the client SPA take over for the rest of the application lifecycle. Because the initial page content is fully loaded in an HTTP request, this technique improves initial load times & SEO discoverability.

There are now a wide variety of frameworks that offer isomorphic rendering functionality. In fact, it’s built-in to some popular frontend frameworks like Vue and React, though setup and maintenance comes with some overhead — isomporhic rendering may introduce new backend dependencies in order to perform the rendering at request time.

Server-side rendering “plus”

SSR with isomorphic rendering helps to solve a set of common issues with SPAs, but a new generation of technologies is taking the idea even further: what if you did all of your rendering on the backend, and effectively eliminated your frontend application.

This is what LiveView and other new tools are enabling: rich & highly-interactive web apps driven entirely from the backend. For the rest of this article, I’ll refer to this approach as SSR+. That’s not a standard term for these next generation SSR projects, but I’ll use it differentiate them from the other, non-interactive SSR solutions.

SSR+: LiveView & Friends

LiveView is a server-rendered library for the Phoenix Framework written in the wonderful Elixir language. When a page is requested, it returns the fully rendered content, just like an old-school web app. But after the page is rendered, a persistent Websocket connection is maintained under-the-hood to dynamically update the page based on the server’s application state.

The Phoenix Framework, featuring LiveView
If you’re familiar with how React components are rendered using declarative templates that update automatically whenever state changes, LiveView works essentially the same way.

If you’re familiar with how React components are rendered using declarative templates that update automatically whenever state changes, LiveView works essentially the same way. The difference is that LiveView runs fully server-side, and the “state” we’re referring to is not some local browser data store (like Redux, for example), but is derived from actual backend server state.

What this means is that as data changes on the backend, views can be automatically re-rendered and delivered to the frontend, without the developer dealing with API calls, polling, synchronization, etc. And just like frontend frameworks that use DOM-diffing to only re-render what’s necessary, LiveView pushes out only pieces of the page that are updated when state changes.

For scenarios where it’s absolutely necessary, LiveView does offer JavaScript interoperability via hooks. Typically these hooks are used for targeted and well-isolated interactions that don’t fundamentally break the server-oriented paradigm. For example, they might be used for implementing animations and other UI interactions, but would not be used to significantly alter page content.

Though I’m using LiveView as the example here, SSR+ projects are available in many other popular languages. This list of server-rendered HTML projectsshows a number of options for a variety of languages. In fact, there are even SSR+ frameworks for JavaScript. Instead of running a JS framework in the browser, you can use JS on the backend to drive your entire application — you can still write JavaScript, if you prefer, but get all the benefits of SSR+.

Benefits of Server-Rendered HTML

There are many benefits, and of course some downsides, to an SSR+ approach. Let’s explore a few.

Simplified state manage — no client/server synchronization issues

One challenge in writing frontend applications is state management, meaning how we store, access & update the data in the application. In a JavaScript SPA, this is complicated by the fact that you’re essentially dealing with two states: the client & the server. Keeping those states in sync requires API calls or other communication between client & server to send or receive data in response to user interaction and other state changes — including interactions that happen on other clients in some applications (think social media, chat, etc).

With SSR+, there’s only one source for application state: the server. Any changes that happen on the server are immediately and transparently pushed out to the client. Likewise, client interactions are immediately handled as evens on the server. The server then simply renders what the state of the client should be, and updates are automatically synchronized to the browser.

Not just a single language — a single application

Some developers like using Node.js because they see a benefit in using a single language on both the frontend & backend. Sometimes there’s even some level of code reuse. This is great in theory, but the reality is that you’re still dealing with two logically separate “applications”: a frontend and a backend which communicate via an API. In my experience, code sharing between the two applications is real, but often not significant.

Using a server-rendered approach, you are truly dealing with a single application for your front- and backend, typically with far less code than required to build them separately. This has massive benefits for the development lifecycle. There’s no time or effort spent on building & maintaining APIs or dealing with synchronization issues on the frontend as those APIs change, not to mention the general overhead of maintaining two projects vs. one.

Escaping from the JavaScript ecosystem

I’ll refrain from editorializing too much on the state of the JavaScript ecosystem, except to say that working with a rich modern JavaScript app carries a great deal of overhead in terms of maintenance, configuration, dependency management, etc.

Using a server-rendered technology can almost entirely eliminate this pain-point. Even if you choose to include a bit of JavaScript in your server-rendered application (which I do recommend elsewhere in this article to solve some specific problems), you can largely avoid the overhead by sticking to simple JS, with no heavyweight application framework (Vue, React, etc) and minimal dependencies.

Faster Load Times & Better SEO

As mentioned earlier, JavaScript single page applications have some downsides when it comes to load times & SEO. Because they rely on the client to render content after the page, and all dependencies are loaded, requiring many server round-trips to fully render. In practice, this means a delay in load times, which in and of itself can negatively impact your site’s SEO.

Furthermore, search engines vary in their level of support for executing JavaScript as part of their indexing. Google, for example, does execute JavaScript, but in a separate second phase which can occur hours or weeks after the initial index. If you produce new content or if your content changes frequently, it won’t be indexed in a timely fashion.

With SSR+, pages are rendered entirely on the backend and available with a single HTTP request, and no JavaScript execution. This has major benefits both to user experience and SEO. Though many JavaScript frameworks do support isomorphic rendering as a solution, the issue simply does not exist with SSR+ as all rendering is done from the backend out-of-the-box.

Challenges with SSR+

In spite of their benefits, server-rendered apps aren’t right for every single use case. Here are some of the challenges you might face in developing an app with the SSR+ approach.

No offline functionality

Some JavaScript applications are designed to support scenarios where a user may have no internet access for an extended period of time. These applications use localStorage, for example, to allow users to access/edit data even when there is no network connection present.

It goes without saying that server-rendered HTML is a non-starter for these kinds of applications. However, applications with strong offline support are the exception, not the rule: most traditional JavaScript frontends are not written to support offline access anyway, so generally speaking, this is not an issue.

Poor network connectivity & latency issues

This is related to the previous point, but represents a much more common scenario: when network connectivity is poor, performance of an SSR+ app suffers as the server is unable to push updates out to the client in a timely fashion.

Even worse, if network connectivity is temporarily lost, the app may become completely unresponsive. This sounds concerning, but is not necessarily much worse than a typical JavaScript-driven frontend: if the backend is unavailable, most single page JS apps won’t function correctly either. What a typical single page JS app probably will do better, though, is show better messaging and UI when the network is unavailable. Things like spinners and error messages go a long way towards improving the user experience, even when nothing is working at all.

One way to mitigate this issue with server-rendered HTML is by using the JavaScript hooks mentioned earlier to handle these offline scenarios. Showing a connection status indicator or an alert dialog can be an effective way of handling network connectivity edge cases in an SSR+ app.

Real-time interactions & animations

Though the performance of server-rendered HTML can be quite impressive, it’s not the best choice for heavy interactions and animations (e.g., 30+ frame-per-second visual updates). Fortunately, CSS animations work perfectly well with a server-rendered HTML approach — by manipulating the class names on elements, you can trigger CSS animations for most interactions. And for those animations that simply can’t be pulled off with CSS, you can always fallback on the use of targeted JavaScript hooks— just enough to pull off the desired effect, while keeping the core application logic server-side.

Frontend integrations & browser APIs

If your application requires direct access to browser APIs, you’re simply going to need to work with frontend JavaScript. In most cases, the SSR+ frameworks’ JavaScript interoperability will enable you to access the functionality you need. But if you’re making heavy use of these APIs, at some point you may find yourself fighting against the server-rendered paradigm and may be better off with a fully client-based JS approach.

Server load & scalability

A benefit of the traditional client/server approach is that you can effectively offload a great deal of processing to the client. With server-rendered HTML, that cost is paid by the server — every meaningful DOM update on the page involves the server pushing updates out to the client. As the number of clients grows, the load does as well.

How big of an issue this is will depend entirely on the complexity of your application. Don’t automatically assume that the performance will be much worse than an API-driven approach: after all, processing API requests, rendering to XML or JSON and then formatting on the client can all be expensive operations as well, so skipping a couple of steps and rendering directly to HTML on the server may not necessarily be any worse. Just be aware of the potential for increased load and monitor accordingly.

Which approach should you use for your next project?

Don’t walk away from this article with the impression that SSR+ server-rendered HTML is the best choice for every project — it’s not. I still use JavaScript everyday, and recently picked React as the frontend on a project because I thought it was a better fit than LiveView. As with every engineering decision, the choice between using SSR+ and a traditional JavaScript-driven frontend comes with a number of tradeoffs. Consider these tradeoffs when designing your app — think about how you want your app to behave, and where you want to invest your time.

Even if SSR+ isn’t the right approach for a project you’re working on right now, I would encourage to keep an eye out for the opportunity to give it a try, just to learn more about how it works and to get a visceral feel for the tradeoffs. Whether end up using SSR+ or not at the moment, it’s a very interesting development in the evolution of web architecture and worth keeping an eye on.