Single page apps are harder than you think.

Published: March 15, 2024
Author picture of Brady Catherman
Brady Catherman
Sultan of Scale

Single page apps are extremely common in the internet. And why not? They are often pretty and very responsive. There are however edge cases that often get overlooked due to the nuance in communication that this style page creates. The goal of this article is to call out some of the issues

What the version?

When testing the page, and likely when doing QA, the page is loaded from the most recent version and tested. In the real world though your users won’t happily reload the page just because you deployed. Often they won’t even know they need to reload and will become disgruntled at how often they have to do a hard reload, or worse close the tab and reopen, in order to fix your page.

So you deployed the new version, and a few days later a user reports a problem that you could have sworn that you already fixed. Sure you can tell them to reload, but wouldn’t it be better if you could just see the version issue in your own logs?

Setup your JavaScript to include the version that is loaded in the browser as a header on all calls to the API. This allows you to log it, and it lets you know how many users have loaded the new page.

Ideally you can also have a reply header that allows the server to inform the browser JavaScript that it needs to reload. This can be done automatically or you can prompt the user depending on the use case (for example, a minor edit of a profile page shouldn’t force a reload, but once the profile has been saved you can force a reload since it won’t interrupt the user too much.)

What race condition?

So you have a good setup, you deploy your JavaScript to a directory and then swap a symlink to switch the served directory. It’s all good right? No partially written files being served, no references to files not deployed yet, etc. Because of the introduction of logic in the browser there are actually still race conditions.

Say the user is loading the main HTML page when the deploy happens, suddenly they have an old page with new JavaScript, or new CSS, etc. The site can be partially loaded with different versions pretty easily because of this. Same can happen if one server is serving old resources and one is serving new resources. This was already a challenge before with the possibility of a resource like a js file, or image asset being overwritten during the deploy phase so this isn’t unique to single page apps, but given the longevity of a loaded single page instance it just becomes far, far worse.

Now I can already hear you saying “I use versioned resources to prevent that issue!” Great, but what happens if the user is attempting to load an old resource because they got a old HTML main page prior to the deploy? If you are using symlinks then the old resources won’t exist at all when the client calls for them!

If you use versioned asset URLs then it’s important to be able to download an old version for a while after the deploy just in case. This can be especially tough if you are using “immutable infrastructure” like Heroku, Docker, or CloudFoundry.

Wait… what version is doing THAT?

So you finished your deploy and now you are seeing a strange cycle of requests. Perhaps some clients are suddenly querying and endpoint way more often then they should, or a specific client is failing every query of a given type, etc. If you use a header as I suggested before you can basically build an A/B test to evaluate the difference between the interactions. It might be that old clients have issues with the new deploy/assets or some such. The errors might not be because of the new code at all. Tracking which versions are interacting with each other will go great ways to helping identify issues.

Cleaning up

Sometimes you have users that just keep the page open so long that the interface is positively ancient. This can be problematic because it means that you have to keep old assets around in case they are needed for quite a long time. Applying the reload header concept you can either have a forced reload header from the API that tells the interface to just stop the user and force a reset. Another option is to have a hard timer on page load that will trigger a force page load after some extended period of time, 30 days for example.

With either of those in place and automatically generated it becomes possible to cleanup old assets after some period of time that you define. Thus you don’t need to keep assets around for years.

Copyright 2016 - 2024