12 min read

The Next 7 Principles of Rich Web Applications

This serves as a preface to kick off a 12-part series called Distilling the Web to Zero, one new essay for each month of 2024.

The web is far from done improving - particularly as it pertains to building rich web applications that are, to users, developers and businesses alike, more desirable than their native-app counterparts. This series defines the biggest challenges on the road towards that goal and explores potential solutions using concrete examples.

NOTE

This is a loving ♥ tribute to the essay that inspired it, 7 Principles of Rich Web Applications by Guillermo Rauch which was written a decade ago. It was a huge inspiration to me personally back then and I love the notion that it laid the foundation for Next.js, Vercel (and more) which have had an incredibly positive impact on the web.

While this series will challenge the status quo of the web and explore very different approaches to web development, in no way is it meant to be a post-mortem or indication of failure by anyone in the web community. I believe the last ten years was exactly what the web needed. However, my intuition is that the next ten years will need something quite a bit different.

Will webapps ever replace the venerated native app? Similar to "the year of Linux on the desktop" this question will remain no more than a meme unless it can address the following 7 principles.

  1. Webapps must become invisible
  2. Webapps must return to the web's beginner-friendly origins
  3. Webapps must resolve their complexity ceiling
  4. Webapps must NOT be built on request/response protocols
  5. Webapps must be built on stateFULL infrastructures
  6. Webapps have NO refresh button
  7. Webapps must NOT be composed of "pages"

Webapps must become invisible

At what point does a website become a webapp? The term PWA (Progressive Web App) implies it's a spectrum, not either/or. This soft stance causes great harm to the webapp community as it encourages users to perceive webapps as merely "fancy websites" and therefore lesser than their native app counterparts. This is a problem.

A cleaner way to categorize the two isn't through the features they employ but rather their approach to design.

Websites use bespoke designs. Webapps use design systems.

For those unfamiliar with design systems, I'll dig in briefly. The most important thing to understand is that they're about more than just styling. Design systems also define behaviors and they do so in a very opinionated and prescriptive manner. For example take Material Design's guidance on chips. To summarize (aggressively): Chips aren't buttons, they are supplemental. They represent forking paths for a current task, while buttons represent linear steps. They can be scrolled horizontally, should always appear in a set, never by itself. They can have leading and trailing icons and must mimic only one of the four allowed characteristics: assist, filter, input or suggest.

Design systems aren't meant to imply other approaches are wrong or lesser. The ultimate purpose is about consistency; it's about embracing a platform's look and feel. Adhering to these rules is what makes an app's UI start to feel invisible because users can hit the ground running in a brand new app when there's zero learning curve. Previously, designers would lean on skeuomorphism as clues for what the user should expect to happen. That's why a button appeared like a physical button -- so users would know they can press it. Today's designers have a more sophisticated strategy: consistency. When each app looks, feels and behaves the same, it shortens the learning curve substantially and the UI starts to fade into the background until it feels invisible. It's like driving home. You don't need to think about how to do it, it's just auto-pilot. Anything that breaks the pattern is not only distracting, it causes confusion and erodes the user experience.

This same phenomenon exists outside of design too. Movie soundtracks have been going through a cultural shift over the past 20 years. Everyone can hum the tune of Jurassic Park or Star Wars but what's the last movie soundtrack you remember? A lot of filmmakers feel that "music should not be noticed." Using a catchy, hummable melody is considered by many to be distracting and removes the audience from becoming fully immersed in the story which is the cardinal sin of storytelling.

The web has yet to benefit from "embracing the platform" like iOS and Android apps have since there is no single "host platform." The web runs everywhere so it's never had an identity of its own. The web is the melting pot of everything.

Websites show something. Webapps do something.

When this is the distinction, and you give each side enough time to evolve into their final forms, websites will be loud and flashy while webapps will have UI that feels invisible. (By no means should the entire web be homogenous, just webapps. Websites can and should make an effort to stand out, be unique, loud and showy. Their purpose is peacocking.)

Webapps must ALL coalesce around a common design system. Until they do, the comfort of muscle memory enjoyed in native apps will be missing from webapps. So long as every webapp looks and behaves differently, even if perfectly designed, people will continue to view webapps as merely "fancy websites" that somehow feel more difficult to use than their trusty native apps.

Webapps must return to the web's beginner-friendly origins

The web's core strength is inclusiveness. It's not a place limited to only corporate juggernauts or the engineering elite. A highschooler with their ten year old laptop can, without anybody's permission, include their own creation as an equal citizen of the internet. HTML's approachable and forgiving syntax, the browser's "View Source," the instant feedback loops helped attract the curious-minded, the tinkerers, the creative-types of the world. This is the ethos of the great WWW and, despite the other challenges it might bring, it's what makes the Internet unlike anything else.

Building a webapp does not have that same ethos. Missing are the curious-minded, the tinkerers, the creative types. The webapp-ladder has been pulled up and been made exclusive to only the engineering elite. Table stakes are no longer just to bring your own laptop. You must now have months or years of training, and this training is never complete since the half-life of a webapp education is roughly 2.5 years. If building a website in the 90s was like driving a car, building a webapp in the 2020s is like piloting the space shuttle.

Many of my colleagues have adopted a "why bother" attitude towards building for the web. We've reached a tipping point where the energy required to participate has outweighed the joy of creation itself. This reality isn't only a tragic loss of what we once had, it's unintentionally exclusionary and anti-web.

To be clear, this is not a criticism of the tools we use today. They're great! I happily use them. I'm glad the space shuttle exists... just not at the expense of cars existing too. Webapp development has reached unprecedented levels of sophistication leaving behind a huge gap between the two worlds of websites and webapps. In its wake, it takes away the most special aspect of the web -- it no longer feels like a place for everybody.

Being beginner-friendly isn't only about ease-of-use. It's equally about longevity, nailing down moving targets, and the balance of standardizing while simultaneously leaving other options open.

It's a tall order but not an impossible task. In fact, it's been done many times before. To name only a few:

  • jQuery bridged us to asynchronous communication until the web eventually made those adoptions papers official
  • TypeScript's introduction of classes was revolutionary and now ECMAScript has native support
  • SPDY was intentionally built as the semelparous organism to bring us HTTP/2

For webapps to reach their final form, standardization committees must think of a webapp as a different animal than a website. Webapps must have web-native support for things like declarative control flow and primitives for state, things a website would never need but are table stakes for the rich web.

Webapps must resolve their complexity ceiling

The complexity issue isn't only about lowering the barrier to entry. There's also the issue of a webapp's "complexity ceiling." Android bumped up against a fascinating one in its early days: Enable multidex for apps with over 64K methods. In short, a single Dalvik Executable (DEX) bytecode file could have a maximum of 65,536 references that can be invoked. (App Compat and Google Play Services CGM alone contained over 16k methods each.) Fortunately, there was an elegant solution. Since an android app is compiled into a .dex file which was zipped into an .apk file it could simply split the code across multiple .dex files inside the zipped .apk.

JavaScript has a payload problem.

Webapps are not so different except their "method count" isn't a hard limit. Worse - it's subjective. A webapp's method count is limited by the user's patience. Since a webapp's destiny is to be downloaded "on demand," there is an timeless battle playing out between two warring factions: speed vs. richness. Native apps enjoy the luxury of both speed and richness since they are downloaded in full only once. Webapp developers, once their app hits its complexity ceiling, instead of being able to fully focus on improvements, spend the rest of their days wrestling with frustrations like code-splitting and sacrificing beloved richness in the name of the TTI gods.

So what exactly is the maximum number of functions a webapp can have? If we follow the guidance of Chrome devs and Martin Fowler, carve out 1MB for JavaScript (the majority of that 1.6MB budget) and assume an average of 150 bytes per function (six lines of code), it equates to 6,991 maximum functions (2^20/150). Even after a 77% compression ratio, which bumps the function count to 30,394, that's still less than half the potential logic of an Android app from 2014. Fast forward ten years later and it's not uncommon to see native apps that measure in the hundreds of megabytes. How exactly does a webapp compete with that?

It's wonderful how much JavaScript has improved over the past decade but no amount of committee steering can fix JavaScript's payload problem. This time, there's no silver bullet. Networks would need to suddenly become 100x faster, and already "the hardware of the Internet can currently achieve within a factor of two of the speed of light." WebAssembly, while wonderful technology, doesn't escape the dreaded "payload problem" either. Thus explains the latest trend where more and more of a webapp's logic is making its way back to the server side. This however, is just trading one set of problems for another. While the trend is yielding some improvements in speed, the process of straddling your webapp across both client and server is burdening the developer with exploding complexity.

Webapps must NOT be built on request/response protocols

Recent trends in webapp development are seeing more and more of a webapp's logic make the great migration back to the server side. With webapps, the overwhelming majority of communication between client and server is HTTP. This protocol used to be the only option. Back in the AJAX days, there was only one way to avoid a full page refresh: make a request, get a response.

As the web community endures its Great Migration and they are found controlling more and more things from the server side, it will inevitably crave richer protocols. With HTTP, the server cannot do anything useful unless the client initiates a request first. What's more, the server can only respond with a single response. Granted it can bundle however many multitudes of data into a single response, but the deeper it travels down that strategy, the longer and longer the latency becomes. Essentially,

"Don't speak unless spoken to."

and

"It's impolite to talk too much."

While HTTP does support streaming protocols which can allow "time-shifted responses," webapps are inevitably going to demand first class support for bidirectional communication. This will unlock a fascinating transformation in webapps. New web frameworks will emerge that take a WebSocket-first approach instead of HTTP-first. Suddenly developing webapps will feel much more like building MMO video games. Exciting, to say the least!

Webapps must be built on stateFULL infrastructures

Being "web scale" has become synonymous with stateless architectures. The siren call of horizontal scaling is hard to resist. Simply throwing more machines at a problem and elastically scaling up and down on demand simplifies many things and has been the default for decades. This works great for business logic which is usually inherently transactional. Websites have been enablers of this pattern too allowing us to kick this can down the road even longer since each distinct page can simply refetch-the-world, render output, and immediately forget everything.

Webapps have a richer UI. They differ from websites by their micro-interactions. Full page refreshes are an anti-pattern when all you need is input validation or a subtle feedback mechanism. Rich UIs are essentially one giant state machine. Building a webapp means constantly wrestling with the dissonance between keeping its own state intact while communicating with business logic that likes to "forget" everything that just happened in the previous volley. It's like trying to have a fruitful conversation with another person who can only remember as far back as your previous sentence. It's possible, just very limiting.

Building a stateFULL architecture isn't new but it's rare enough that our usual choices of cloud infrastructure make doing so downright hostile. For example, RAM costs in the ballpark of $200/GB per year in the cloud. For comparison, buying your own RAM stick is closer to a $2/GB one-time expense.

It's not all about state either. Bidirectional communication is essential if you're building UI that responds to more than just the inputs of the user in front of the screen. The WebSocket protocol was standardized in 2011, 13 years ago, but if your plan is to use cloud functions instead of managing your own VMs, the pricing model is still based on "wall-clock" time, not CPU time equating to roughly to $0.10/hour per user. It'd be like running your cloud function in an infinite loop for every tab of every browser currently using your webapp. This isn't nefarious on the part of the cloud providers, it's just because your cloud function cannot both end its execution AND keep that connection alive. It's as if they assumed nobody would ever want to use a WebSocket.

Webapps need RAM and bidirectional communication channels to reach their full potential but the costs need to come down by 100-1000x before businesses will prefer them over their usual basic websites.

Webapps have NO refresh button

Certainly the browser isn't going to remove the refresh button. But webapps' final form will render it useless. Pressing the refresh button will look like it did absolutely nothing since the entire UI is already kept perfectly in sync.

This might feel a bit foreign in 2024 since we have become so very accustomed to hitting that refresh for a quick fix. GitHub is notorious for this since it's use case is filled with many separate editable components. Change the name of a label? The reflexive action is to refresh your tab to get the latest changes. Got two tabs open at the same time? Refresh to "get the latest changes."

You wouldn't expect to need refresh a calculator app. Native apps have no refresh button. Webapps must join them. (This is not the same as "pull-to-refresh." That is a UX design mechanism that prevents content from shifting around while you're trying to read it.)

A refresh button makes sense for reading pages on a website but it is unnatural and learned behavior for an app. We might not even realize when we're doing it but subconsciously it causes our brains to think, "This is just a web page."

Webapps must NOT be composed of "pages"

Webpages are synonymous with the web and always will be. They directly correlate to a clean, globally-unique URL which is truly universal and platform-agnostic. The URL might be the smartest thing about the web. Native apps don't even have it as good when it comes to URLs. While they can do some form of deeplinking, it truly is an afterthought, unreliable and unpredictable. +1 for the web!

That being said, web pages are the #1 most limiting factor for building webapps. Any webapp composed of pages has no hope of ever being perceived as more than just a "fancy website." The strategy of starting with a page only to enhance it with smaller dynamic chunks, does improve the UX, but somehow never is able to suspend disbelief in the user that it's somehow "not quite native." "Pages" are just too blunt an instrument. It's like trying to cross stitch with a bulldozer.

Rich apps too often share state between two or more pages. For example, previewing an image might be displayed in a shadowbox because you clicked its thumbnail and going back needs be animated except when linking to the same URL from an external site foregoes the sandbox and displays it full screen because hinting about extra back state is confusing to the user who didn't ask for all that extra "back history." They're expecting the back button to return to the linking site, not that image's parent navigation. Or maybe you have a dashboard that shows multiple panels. Maybe each panel has its own "callout details" that might show anchored to the right or bottom but only if the window is large enough and if it's shrunk then its "callout details" have to be physically attached as a expanding child of the originating "card". Maybe there's two of the panels, what about three? To what page exactly does that URL correlate?

For webapps URLs are here to stay, but they simply cannot correlate to indivisible pages under the hood. App state is just too fluid and ephemeral for that. Instead, webapps must treat URLs as state-bookmarks. They must serve two purposes

  1. To deeplink into a specific, deeply nested starting point of the webapp, like a specific product or message.
  2. Play nice with the back button to unwind conceptual steps or navigation. Be warned, this can get very complex. Just ask any old Android developer about the differences between "back navigation" and "up navigation." (Fun fact: iOS was able to skip that mess because they didn't have an always present back button like Android and the web.)