You should consider making your web app offline-first
Most web application developers reach for the tried and tested approach of fetching data from their API right when they need it, and they don't give an offline-first approach the consideration it deserves. Perhaps that's because working offline is generally thought of as a specific feature and that it's not necessarily applicable or appropriate for a wide variety of applications. However, there's a lot to be gained from going offline-first, even if your app isn't the "typical" offline application. Hopefully, after reading this article, you'll feel like you're better equipped to answer if working offline-first is right for your app.
First things first - what do we mean by "offline-first"?
When we say "offline-first", we don't just mean a bit of caching that means your user can revisit some pages they've already seen before. Many web apps make heavy use of the browser cache to achieve some basic level of functioning without a working internet connection. We're talking about going well beyond that.
For us, an offline-first web application is an application that is designed to be (close to) fully-functional without an internet connection. Now of course, the application may need an internet connection the first time around to authenticate the user and download some data. However, after that initial sync, it should be able to function without internet connectivity for some period of time. This means that not only browsing data (i.e. read operations) should work, but the user should also be able to create and update data (i.e. write operations) while in a disconnected state.
There are a million and one ways to achieve this kind of offline-first functionality. There's the browser cache, service workers, local storage, IndexedDB and a myriad of other tools that can be brought together to give your users a brilliant offline experience.
Inspired by the offline-first tools we use every day like Superhuman, Figma and Pitch, we've recently been working hard to turn Kitemaker, our product management tool for cross-functional, self-organizing product teams, into an offline-first application. We fetch the data the user may need ahead of time and store it in a local cache. The web application itself operates on that local cache only, and a separate layer takes care of syncing those changes back to the server. Our design was heavily inspired by the work Evan Wallace published about how multiplayer behavior works in Figma. We're currently working on using service workers to download static assets and using IndexedDB to back our cache with persistent storage. We'll do a follow-up blog post on how offline works in Kitemaker, but here's a quick peek at the architecture:
But this is just one example of how to achieve offline functionality. Exactly how you solve it will depend heavily on your application. The important part is that you start to think about whether your app should be offline-first.
Is offline-first appropriate for my application?
You may be thinking that offline functionality sounds nice, but isn't super relevant most applications. We tend to disagree. We think there is a trend to push more and more SaaS products towards supporting working offline, and we think this is absolutely the right idea.
- Is your application's performance (how fast it responds to your users' inputs) important/critical?
- Will your users be using your application in areas with spotty connectivity?
- Will your users by using your application on airplanes?
- Will your users be using your application when roaming on their data connections, such as when on vacation?
We'd be surprised if you didn't answer "yes" to at least a couple of those questions.
What can offline-first do for me?
There are a bunch of advantages that come along with making your application offline-first:
- It'll work offline. Yep, that's right. If you make your application work offline, it will in fact work offline 😊 It's actually a pretty killer feature and one which we appreciate greatly, especially when traveling/commuting.
- It'll survive flaky networks. There's nothing worse than getting an error on a web application just because your network had a hiccup. We really wish more chat applications (you know who you are) worked offline-first to avoid bad behavior on bad networks.
- It'll make your application react quicker to your users' inputs. Instead of waiting for a server roundtrip to update a UI, offline-first applications can behave optimistically and update local data right away and then send off changes to the server. This means your users don't need to wait around for the response.
- It sets you up nicely for "multiplayer" behavior. In order to make your application work offline, you need to be able to handle conflicts that might arise when your users come back online and start shipping queued changes to your servers. Handling these queued operations is very often the same type of logic that's needed to handle two users operating on the same data at the same time, which means you might be able to unlock more collaborative features of your application.
- It can help you scale your writes on your server. Because your users aren't waiting around for responses from the server, you can treat your writes a bit differently. Maybe you queue them up for processing, or adopt a CQRS and event sourcing approach. It opens up all sorts of possibilities.
- It can simplify your app logic. For us in Kitemaker, this was a big one. Before we adopted this offline-first approach, our GraphQL API calls had wormed their way into our code in a lot of places. Whenever changing data, we had to set up a GraphQL mutation, handle the response, provide optimistic responses to get the performance we need, etc. It was messy and complex. Now we just operate on objects in our local cache and the changes are transparently synced to our backend (and onwards to other users).
- It can simplify your backend API. This one is not guaranteed by any means, but for us, our backend API became a very simple thing indeed. Instead of create/update/delete endpoints (or in our case mutations since we use GraphQL) for all of the different resources in our system, we now have a really simple API for sending changes from the client to the server. That's it. Of course YMMV.
What are the pitfalls?
While there are numerous advantages to working in an offline-first, it's not a given that you should dive in right away. Think about these things a little first:
- Is the data your users need to interact with reasonable small or can you chunk it into smaller datasets that your users can selectively choose to download for offline use (e.g. restaurant recommendations for a particular country/city)? If not, it will be very hard to build a good offline experience.
- Can you ensure that most operations that your users perform while offline will succeed? Being offline-first does not lend itself to applications where your users will frequently get their requests rejected, transactions will be get rolled back, etc. Basically you need to ensure you have a very simple and deterministic way to handle any conflicts that might occur. If you can't ensure this, you'd better think twice before digging in.
- It can be a lot of work to get it right. There are tools out there to help you, but you won't get offline-first behavior for free. You need to decide if it's worth the investment for your product or not.
Wrapping it up
We hope this article got your gears turning a little when it comes to offline-first web applications. We don't think appropriate for every application - it can be quite a lot of work to get right and for some applications it just doesn't make a lot of sense. But we just think people should give it some thought when designing and architecting a new application. We've learned from experience that retrofitting this type of behavior can also be quite a lot of work, but we're super happy we did it nonetheless!
And if you're a software product team that aspires to work in a more cross-functional, autonomous, self-organizing and impact-driven way, sign up for Kitemaker. We've built it for teams just like yours.