Weekly Timber is a client of mine that provides logging services in central Wisconsin. For them, a fast website is vital. Their business is located in Waushara County, and like many rural stretches in the United States, network quality and reliability isn’t great.
Wisconsin has farmland for days, but it also has plenty of forests. When you need a company that cuts logs, Google is probably your first stop. How fast a given logging company’s website is might be enough to get you looking elsewhere if you’re left waiting too long on a crappy network connection.
I initially didn’t believe a Service Worker was necessary for Weekly Timber’s website. After all, if things were plenty fast to start with, why complicate things? On the other hand, knowing that my client services not just Waushara County, but much of central Wisconsin, even a barebones Service Worker could be the kind of progressive enhancement that adds resilience in the places it might be needed most.
The first Service Worker I wrote for my client’s website—which I’ll refer to henceforth as the “standard” Service Worker—used three well-documented caching strategies:
- Serve static assets out of
CacheStorageif available. If a static asset isn’t in
CacheStorage, retrieve it from the network, then cache it for future visits.
- For HTML assets, hit the network first and place the HTML response into
CacheStorage. If the network is unavailable the next time the visitor arrives, serve the cached markup from
These are neither new nor special strategies, but they provide two benefits:
- Offline capability, which is handy when network conditions are spotty.
- A performance boost for loading static assets.
A better, faster Service Worker
The web loves itself some “innovation,” which is a word we equally love to throw around. To me, true innovation isn’t when we create new frameworks or patterns solely for the benefit of developers, but whether those inventions benefit people who end up using whatever it is we slap up on the web. The priority of constituencies is a thing we ought to respect. Users above all else, always.
There are certainly other challenges, but it’ll be up to you to weigh the user-facing benefits versus the development costs. In my opinion, this approach has broad applicability in applications such as blogs, marketing websites, news websites, ecommerce, and other typical use cases.
All in all, though, it’s akin to the performance improvements and efficiency gains that you’d get from an SPA. Only the difference is that you’re not replacing time-tested navigation mechanisms and grappling with all the messiness that entails, but enhancing them. That’s the part I think is really important to consider in a world where client-side routing is all the rage.
It’s tempting to write off support for older browsers as not worth the effort, or to dismiss network issues as temporary glitches that resolve themselves after a refresh. But often it’s trivially easy to build features in a way that’s gracefully resilient to these types of problems.
The pushback I get usually takes the form of “Well, that approach is fine for websites, but it wouldn’t work something like Gmail.”
It’s always Gmail. Which is odd. Because if you really wanted to flummox me with a product or service that defies progressive enhancement, I’d have a hard time with something like, say, a game (although it would be pretty cool to build a text adventure that’s progressively enhanced into a first-person shooter). But an email client? That would work.
Progressive enhancement isn’t about making a choice between using simpler more robust technologies or using more advanced features; it’s about using simpler more robust technologies and then using more advanced features. Have your cake and eat it.
Fortunately I no longer need to run this thought experiment to imagine what it would be like if something like Gmail were built with a progressive enhancement approach. That’s what HEY is.
HEY’s UI is 100% HTML over the wire. We render plain-old HTML pages on the server and send them to your browser encoded as text/html. No JSON APIs, no GraphQL, no React—just form submissions and links.
If you think that sounds like the web of 25 years ago, you’re right! Except the HEY front-end stack progressively enhances the “classic web” to work like the “2020 web,” with all the fidelity you’d expect from a well-built SPA.
See? It’s not either resilient or modern—it’s resilient and modern. Have your cake and eat it.
And yet this supremely sensible approach is not considered “modern” web development:
The architecture astronauts who, for the past decade, have been selling us on the necessity of React, Redux, and megabytes of JS, cannot comprehend the possibility of building an email app in 2020 with server-rendered HTML.
Their focus is very much on people above technology. They’ve taken a human-centric approach to their product and a human-centric approach to web development …because ultimately, that’s what progressive enhancement is.
In September of 2019, Zach Leatherman tweeted
Which has a better First Meaningful Paint time?
- a raw 8.5MB HTML file with the full text of every single one of my 27,506 tweets
- a client rendered React site with exactly one tweet on it
(Spoiler: @____lighthouse reports 8.5MB of HTML wins by about 200ms)
Take a moment to wrap your head around that.
It’s perceivably faster to load 8.5 megabytes of HTML than it is to load a single tweet with a client-side React app.
Real companies can and do build apps with HTML first
Neither product is really my cup of tea, but what I find super interesting is how Hey is built.
It’s core is server-rendered HTML. Basecamp is a Ruby on Rails shop (their CTO created Rails). Almost every view in the app is created on a server.
Then, they sprinkle just a little vanilla JS on top to turn things up to 11.
By using this approach, if the JS fails or isn’t supported, the app still loads and works and gives people the full experience. It also means you don’t have to wait for the full JS package to load before you can start using the app.
You still get the benefits of faster page loading that SPAs sometimes give you, but you don’t have to maintain two code bases or do complicated server-to-client hand offs (“rehydration” as they call it in the React world).
The Pale King, the late David Foster Wallace’s final novel, was published in 2011. It’s about IRS employees in Peoria, Illinois
And it’s incredibly boring.
I’m not being cruel — The Pale King contains an intricate description of a traffic jam, a long list of tax forms, and an entire chapter devoted to mundane office tasks. As Michael Pietsch, the book’s editor notes, “David set out to write a novel about some of the hardest subjects in the world — sadness and boredom.”
Defending boring UX is a slightly easier task. I just want to get you excited about the invisible, unsung work required to build useful and understandable digital products that truly satisfy user needs.
Boring user experience is clear and straightforward content, design, and code that solves key pain points. No surprise. No delight. It’s the non-design of IA Writer or the simple poetry of plain language.
Unboring is an error message that requires a PhD to unpack or Microsoft Word’s everything-plus-the-kitchen-sink approach to software.
Boring UX emerged in 2012 with the launch of GOV.UK, a government site that proved straightforward digital design can improve the lives of millions of people. As the jury who awarded the site Design of the Year 2013 put it, “It may not look particularly exciting or pretty, but that is not the point. This is design in the raw, providing vital services and information in the simplest, most logical way possible.”
Boring doesn’t always save lives, but it usually improves them. The titans of the web — Wikipedia, Reddit, Google, Amazon, Dropbox, GitHub — look boring when compared to Snapchat, The Outline, or Bejeweled. But boring companies have millions of repeat users because their products actually work.
“Only when a product is functional, reliable, and usable can users appreciate the delightful, pleasurable, or enjoyable aspects of the experience,” notes Fessenden. In other words, boring underpins delight — and sometimes boring is delightful. Popular apps like Pocket and Instapaper, along with Safari’s reader view, turn exciting into boring by rescuing content from the evil clutches of hyperactive design and indestructible retargeting ads.
If you’re truly user-centric, admit that the most meaningful life stuff happens beyond the borders of tiny glowing rectangles. UX folks are brokers and intermediaries, not rock stars or ninjas. Your job is to swallow some boredom so people can live better lives.
And if that sounds a bit dreary, remember the words of David Wallace in The Pale King: “If you are immune to boredom, there is literally nothing you cannot accomplish.”
For years, we studied teams rolling out new designs, to see if we could mitigate negative reaction to new releases and design changes. We studied hundreds of product and service rollouts. We watched and learned from the reactions of thousands of users.
When we dug into what those users’ reactions [were], patterns emerged. The users told us the changes inconvenienced them. They had no idea the change was coming and suddenly it was in their face. Users were upset because they were surprised.
They also told us the old version worked fine. Even when it took a while to get comfortable, they learned it. Many users mastered difficult-to-use designs.
Everything was different when the new version arrived. What they’d mastered before didn’t help them now. The company said it was an improved design, but they couldn’t see the improvements. Why should these users learn something new that doesn’t help them? Users were upset because they couldn’t see the value.
We also saw many instances where users didn’t react negatively to changes. Often, they didn’t react at all. We saw new designs that didn’t affect the users’ behaviors and they didn’t pay attention to it.
In these cases, the changes were often not noticeable. Sometimes the changes were small and isolated. Yet, we also saw users seemingly not notice several updates with extensive changes. (In more than one instance, an entire application’s infrastructure had been rewritten without a single user noticing.)
In cases when the design changes were noticeable, the designers gave the users control to switch when they wanted. The designers showed why the change was valuable to the users. And the designers made the transition easy by taking the knowledge and experience their users already had with the product into account.
RAIL is a user-centric performance model that breaks down the user’s experience into key actions. RAIL’s goals and guidelines aim to help developers and designers ensure a good user experience for each of these actions. By laying out a structure for thinking about performance, RAIL enables designers and developers to reliably target the work that has the highest impact on user experience.
Every web app has four distinct aspects to its life cycle, and performance fits into them in different ways:
RAIL is a lens for looking at a website’s user experience as a journey composed of distinct interactions. Understand how users perceive your site in order to set performance goals with the greatest impact on user experience.
- Focus on the user.
- Respond to user input in under 100ms.
- Produce a frame in under 10ms when animating or scrolling.
- Maximize main thread idle time.
- Load interactive content in under 5000ms.
If you find yourself wrestling with CSS layout, it’s likely you’re making decisions for browsers they should be making themselves. Through a series of simple, composable layouts, Every Layout will teach you how to better harness the built-in algorithms that power browsers and CSS.
If you’re looking for an example of exactly what not to do in terms of front-end performance, I can’t think of a better one than this - they threw away a lot of the performance optimizations browsers give us for free in a bizarre attempt at improving page loading, which ended up doing the opposite:
- immediately applying
display: none;to the
- waiting until the very last of the page’s images had arrived;
- once they’d arrived, removing the
display: none;and gradually fading the page into visibility.
Not only does this strike me as an unusual design decision—setting out to build a lazyloader and then having it intentionally block rendering—there had been no defensive strategy to answer the question: what if something goes wrong with image delivery?
‘Something wrong’ is exactly what happened. Due to an imperfect combination of:
- images being completely unoptimised, plus;
- a misconfiguration with their image transformation service leading to double downloads for all images;
…they’d managed to place 27.9MB of images onto the Critical Path. Almost 30MB of previously non-render blocking assets had just been turned into blocking ones on purpose with no escape hatch. Start render time was as high as 27.1s over a cable connection1.
If you’re going to build an image loader that hides the whole page until all images are ready, you must also ask yourself what if the images don’t arrive?