4 steps to design fast experiences
This is how it goes. We put a load of shit into a single web page. This makes the page slow. Slow to load, slow to render. Slow.
Instead of getting rid of the shit, we blame the page refresh. There’s only one way to avoid the page refresh and that’s AJAX.
However, AJAX still needs to render new (parts of) screens. More crucially, it still makes a request to the server. That’s not all—there are penalties in using AJAX and it frequently results in slow experiences—not faster ones.
Firstly, making requests; handling different responses; traversing document trees; and injecting HTML requires more code to be sent initally. And then the code needs to be evaluated and executed.
Secondly, AJAX engineers away progressive rendering—which browsers provide natively for free. To reinstate this functionality we resort to hacks which is more code. In reality nobody uses the hack anyway.
Thirdly, using AJAX means we forgo the browsers familiar, accurate and consistent loading indicator. With AJAX, we need to design and build a custom one. Not only is this more work and code, but they are usually an inferior replacement for those provided by browsers.
AJAX is not bad per se. It can be useful in some circumstances as it avoids requesting the same assets repeatedly and may well render faster if it’s a small update to the page like adding products to basket perhaps. But it’s not a solution to heavy, slow-loading pages.
The real problem is that we’ve designed something that can never be fast. So the question should be how do we design for performance?
1. Simplify the interface
The best way to make pages fast, is to have less stuff on them. I’ll forgive you for wanting to punch me in the face as this is obvious. And yet web pages keep getting bigger.
Do we need background videos, modal dialogs and social media buttons plastered everywhere? The answer from the people is a no. The fastest feature is one we never build.
What about hamburger menus, tabs, carousels, accordions, image galleries and expanding panels. All these things have one thing in common. They hide stuff but hiding stuff should be a last resort.
We’re obsessed with saving space and making a clean interface. A clean interface is good but not at the cost of clarity. If pages only contain the essential, then there should be little and maybe even nothing to hide.
Effort aside, designing fully responsive and accessible components results in more code that users rarely appreciate. After all, it slows down the page and requires the user to exert energy to reveal the hidden content.
Heydon Pickering coined the seemingly satirical term ‘unprogressive non-enhancement’. He says:
You take some structured content, which follows the vertical flow of the document in a way that everyone understands.
Which people traverse easily by either dragging their scroll bar with their mouse, or operating the keyboard using the up and down keys, or using the spacebar.
Or if they’re using a touch device, simply flicking backwards and forwards in that easy way that we’ve all become used to. What you do is you take that, and you fucking well leave it alone.
Letting things stack naturally is good. Not only does this embrace the way the web works—it makes for a remarkably simple and fast experience with less effort.
A fast experience is a cornerstone of accessible design because some people don’t have fast connections and this shouldn’t cause exclusion.
But we can do more than just not hiding stuff. We can chunk stuff across pages. Once pages have less on them the page refresh ‘problem’ is no longer a problem. Pages are fast by design. Sometimes to the point where the page refresh is unnoticeable.
Long, complex forms (or even shortish ones) should follow one thing per page.
But this approach isn’t just for forms. Product pages, for example, contain an image carousel, description, add to basket form, shipping information, related products, ratings and comments. We can split some of these things up.
Most users don’t read every single thing on a page on every visit. Instead give users a lightweight page and a clear information architecture that makes it easy to drill down. This is called information scent.
Using the natural building blocks of the web as a form of progressive disclosure speeds things up drastically.
People on expensive data contracts benefit too. They can choose to see all the images by following the link or they can wait until they are connected to WI-FI.
Also, it’s easier to share content. Sending users to a page where most of the stuff is hidden is problematic.
Tabs: if stuff is hidden by default, how important is its existence on this page? Or if the tab contains a small amount of content just show it. Or consider putting it on a separate page.
If you’re feeling brave, put your site’s navigation menu on a separate page too. The page is light and loads quickly. Maybe this is too far. Maybe not.
Modal dialogs are often misused. All too often they contain too much content that would be better off as a new page. This improves performance and doesn’t break the back button like a dialog often does.
2. Write less code
By simplifying the interface we’ve already reduced the code by literally not writing any. But coding the remaining features leads to pillar number 2—write less code.
Use lean and semantic HTML
Sometimes developers use the wrong element. For example, a
<button> is half the size of
<div role="button" tabinidex="0"> and doesn’t require script to make it accessible again.
Divitus is an antiquated buzzword that’s still prevalent today. We often use additional elements unncessarily which make payloads larger, and requests longer. We should be able to justify the existence of every element.
Similarly, classitus is the use of extra classes. Extra classes may decrease the size of CSS but they signifcantly increase the size of HTML.
Ensuring HTML is small is important. Unlike CSS, it can’t be easily cached. This is because HTML is likely to contain personalised and dynamic content which in turn encourages the misuse of AJAX.
Adding attributes for accessibility purposes could actually be completely unnecessary while also creating bloat.
Similarly, the first rule of ARIA is not to use it. To associate errors to a form control we might use
<label for="age">Age</label> <div id="age_error">Enter your age</div> <input id="age" aria-describedby="age_error">
Instead putting the error in the label is more performant and accessible:
<label for="age"> Age <div>Enter your age</div> </label> <input id="age">
Using HTML attributes to automagically initialise script increases the size of the HTML and has other problems too.
Don’t add HTML hooks just for functional tests. Instead use, multipurpose, semantic hooks.
Simplify your design system
GDS (Government Digital Services) design simple interfaces. Most components are left aligned and stack naturally. In this case we may be able to avoid CSS classes altogether which further proves that a simple interface is a performant one.
Use less script
Single pages applications don’t necessarily render faster. And they have a lot of downsides. It takes a lot of code to create a robust client-side application, that typically causes people to employ large frameworks.
But maybe we don’t need the whole framework. That means users have to download a load of code they don’t need and we need to maintain stuff that we don’t need.
Twitter’s sharing button script weighs 50k. But we can do the same thing with 0 bytes by using a simple link.
Use preprocessors responsibly
Use content breakpoints
Often a module may need just one breakpoint or even no breakpoints. Designing to a predefined set of breakpoints encourages the unnecessary tweaking of a design that results in more code.
Not everyone has access to the world western web on high-end devices. But if you can still justify sending users high resolution images:
4. Backend stuff
Enable chunking and progressive rendering. Don’t engineer it away.
Use Command Query Responsibility Segregation (CQRS) to make database queries fast which is good when you have more reads than writes.
Use a Content Delivery Network (CDN) for your static resources. And cache HTML and AJAX responses too.
Cache assets with long expiry dates so that users don’t have to download assets again.
Place scripts at the bottom and use
defer attributes. Async is good for completley independent scripts that can run later like analytics.
Use HTTPS over HTTP2 with Gzip compression. Gzip, by the way, works better with a well-designed and consistent design system—the more HTML is repeated the better the compression.
Use preload and prefetch where appropriate. Addy Osmani says preload resources you have high-confidence will be used in the current page. Prefetch resources likely to be used for future navigations across multiple navigation boundaries.
You know what’s better than perceived performance? Actual performance. Avoid techniques that merely provide a mirage of speed.
Instead, declutter and optimise the foundations of your design system which will result in less weight, less complexity, less distraction, less hassle and ultimately, less bull shit.
Together, these techniques produce fast and simple experiences that make users feel awesome.