Server-side rendering is only half a solution

We live in a fallen world. We are surrounded by faults, some of which we may not notice – others stop us in our tracks. The severity of some faults is dependent on context.

For example, a crack in the windscreen of a toy car is unlikely to cause much consternation. A crack in the windscreen of a space shuttle is more serious. When cooking, a little too much chilli in your con carne and a fussy child won’t eat it. A little too much nut in a supposedly “nut-free” factory can lead to many people being badly affected. Context matters.

On the web we have these three technologies:

  • HTML
  • CSS
  • JavaScript

One of these is not like the others. HTML and CSS are declarative: they are just hints to the browser about how content should be displayed. If the browser hits a fault of any kind it tries to recover itself, and for the most part succeeds. Syntax errors, missing files, DNS issues, unknown properties and elements; all of these faults and more are soaked up by the forgiving nature of HTML and CSS parsers.

Not so with JavaScript. With great power comes great responsibility, and an imperitive technology like JavaScript – which dictates to the execution environment exactly what it should do – is designed to fail if any faults are encountered. This is right and proper; it would be hard to use a programming language which continued merrily on its way whenever a fault occurred.

So, we use Progressive Enhancement principles to ensure we’re creating web sites which are not brittle and will be resilient to the faults which they will inevitably encounter. You’ve heard me preach about this stuff many, many times before.

One of those principles is to use server-side rendering, which means that the initial response for a web site should be a populated HTML document, not just an empty shell. This is a no-no:

<!doctyle html>
<html>
	<head>
		<title>My Cool App!</title>
	</head>
	<body>
		<div id="app"></div>
		<script src="app-all-the-things.js" />
	</body>
</html>

Server-side rendering is a win for performance, as well as ensuring your web site isn’t entirely dependent on JavaScript for its initial render. But there’s a danger here; that we treat server-side rendering as a complete solution to protect us against ALL possible JavaScript failures. Believing server-side rendering to be a panacea is a mistake.

Progressive Enhancement isn’t just about the initial response, it applies to the entire lifecycle of a page: whether that’s a traditional page of content, or a view of a “Single Page App”. Because, in a runtime environment you as a developer don’t control, errors can happen at any time. Not just in the initial render, but even while the user is interacting with the page.

This is often because of 3rd party scripts, but can also be caused by problems caused by your own code. For example, a line of JavaScript being executed which the browser doesn’t understand, or a failure of an API request. As professionals we try to mitigate against such faults, but they will happen anyway despite our best efforts because we don’t control the runtime environment of the browser.

So as these on-page faults will happen, what can we do? In the words of Stefan Tilkov:

…build a classic web application, including rendering server-side HTML, and use JavaScript only sparingly, to enhance browser functionality where possible.

Yes, we go old-school. We use <form> and <a> elements just as if JavaScript doesn’t exist. We handle form submissions and routing on the server, just as if JavaScript doesn’t exist. Because – when an on-page fault occurs – JavaScript doesn’t exist for that interaction.

So, render your content server-side; it’s a sensible thing to do. But don’t forget that the rendered HTML must be functional even if everything else breaks. Going back to our example page above, you could server-side render content like this (truncated) example:

<!doctyle html>
<html>
	<head>
		<title>My Cool App!</title>
	</head>
	<body>
		<div id="app">
			<h1>My Cool App!</h1>
			<p>Choose a filter and upload your image below for fun and good times!</p>
			<div id="filters"></div>
			<div id="image"></div>
		</div>
		<script src="app-all-the-things.js" />
	</body>
</html>

Yes, the content is rendered, but the app still isn’t usable unless all the JavaScript downloads, parses and executes correctly. What you’ve provided the user is not nothing, as in the previous example, but it’s not functional either.

If you provided a server-side rendered HTML page containing a form that was functional irrespective of whether any additional resources on the page worked correctly (and I’m including images, CSS as well as JavaScript) then you’ve implemented your functionality in the simplest possible technology and protected yourself against unforeseen faults. Like this:

<!doctyle html>
<html>
	<head>
		<title>My Cool App!</title>
	</head>
	<body>
		<div id="app">
			<h1>My Cool App!</h1>
			<p>Choose a filter and upload your image below for fun and good times!</p>
			<form action="/imagify" method="post">
				<p>
					<label for="filters">Choose a filter</label>
					<select id="filters" name="filters">
						<option>Catify</option>
						<option>Dogify</option>
						<option>Horsify</option>
					</select>
				</p>
				<p>
					<label for="image">Choose an image</label>
					<input id="image" name="image" type="file" />
				</p>
			</form>
		</div>
		<script src="app-all-the-things.js" />
	</body>
</html>

The great news about this approach is it doesn’t prevent you going absolutely crazy with the very latest bells and whistles! You can use all the modern JavaScript techniques you like (checking that the browser supports them, of course) while knowing that your trusty HTML and server-side logic is the safety net. It bakes resilience into your app at the foundational level.

I hope I’ve given you some food for thought, and demonstrated that while server-side rendering is a good thing to do it’s not the be-all-and-end-all of Progressive Enhancement. You, the developer, should think about ALL the ways in which faults could affect your users throughout the entire lifecycle of the page.