Skip to content

Yoyo Code
Matyáš Racek's blog


Perfect web app

I want to have some reference point for developing web apps. Some ideal to strive for and a way to assess if I am on the right track. A perfect web app, described by user experience, and from that derived technically.

I want to focus on the common hard parts here. There are types of web apps that are basically solved problems (e.g., static sites like this one), that's not what I'm talking about. There are also local heavy apps, like Figma or browser games like agar.io. Those have some unique challenges compared to typical web apps, so I won't focus on their specific problems.

The challenging ones I have in mind are apps where server owns most of the state and clients read and mutate it. This is where all the complexity creeps in. Probably the most iconic example is a social network, but e-commerce sites and technical dashboards are similar, too.

User POV 🔗

From a user's point of view, the app should have:

  1. Very fast first load (all the Web Vitals metrics maxed)
  2. Subsequent navigation should immediately show the content of the next page
  3. Every action has an immediate response
    • clicking on a button should immediately make it clear that the button was successfully pressed, and the action is having an effect. Either immediately or indicated by some progress metric
  4. If something is loading, there should be a skeleton of it
    • instead of a single big flash, the work should provide feedback sooner and progressively change to desired state
    • skeletons should match the resulting page very precisely, no layout shifts
  5. It should work in a limited way offline
    • and give a clear indication of what's working and what's not
    • actions should be persisted locally and synced back when possible
    • all this mechanism should be understandable and predictable for the user (no confusion about what is saved)
  6. Going back should truly put me back where I was
  7. The app shouldn't block user on page switches when some action is being done
    • If I am uploading a big file, I should be free to move around the site
  8. The app should work without JavaScript enabled (progressive enhancement)
  9. The app is accessible (usable by screen readers, navigable by keyboard, has good contrast, all the usual things)
  10. No layout shifts. Content loads by filling a skeleton from top to bottom
  11. The app should be very resilient to errors
    • If I do some action, I want to be 100% sure that it will be handled reasonably.
    • if it's impossible to apply the action immediately, it should be done when possible
    • app should notify me when the error happened and the action is impossible to finish
    • the error should be descriptive and in user's language. As a User, I must understand what the error implies and how to proceed or fix it.
  12. No content swaps
    • swapping the content on the page unexpectedly without a user action is unacceptable
      • currently, this often happens when a user navigates the page and the content is automatically refreshed
      • this is what useQuery does if it's used for dynamic content, and the data change is not handled well
    • ideally, the content should be updated in the background before the user visits the updated page
    • or the user should be offered an option to refresh or add new the data, if new data is available (like reddit does it)
  13. It should degrade gracefully in old browsers
    • the only missing features are ones that are literally impossible to implement in older browsers in a functional way
  14. Layout and content changes are animated to reduce user confusion
  15. Animations are not janky and make the interactions smoother, not adding unnecessary latency
  16. The app is multilingual and the language can be changed at any time without reload

Many of these requirements are pretty hard to meet, especially on low-end devices. That's intentional, this is a perfect web app, and making one is hard. But we should be getting closer to this ideal.

Technical POV 🔗

The reason I am writing this is that I feel like as app developers, we need a lot of help to make this achievable. I think we need something like a framework. Sadly, most frameworks these days focus on relatively little part of this process. So here I'll write how the app has to work technically, and if you're building a framework, you can assess how well it helps with those things and if you're moving in the right direction.

  1. The initial render should happen on the server
  2. It should use AJAX requests for interaction and fallback to native forms/links if the JS is disabled or failed to load
  3. The app should speculatively preload data to reduce latency
    • ideally, when you're on a page, the app will download some piece of data for all pages that are currently reachable
    • this system should be smart, the amount of eagerly loaded data should be proportional to the likelihood of using that data
    • the amount of speculative data loading should be tweak-able, based on various conditions
      • e.g., if the bandwidth is low, or we need it for something else, we should preload less
    • the data should stay up to date
  4. It should have a robust optimistic update system
    • every (eligible) action should be queued, saved locally and sent to the server
      • if the server is busy, we retry, based on the local copy
      • if the user "undoes" the action, we will undo it locally and send an update to the server
      • this system should be resilient to and handle it in an expected way (last one wins)
      • it should also handle data reloads and reconcile conflicts in a predictable way
  5. The app should be well updatable on the fly (no "loading chunk failed")
    • when a new version is published, pieces of the app are replaced, or the page is refreshed in a non-disruptive way. User should never be interrupted and should never notice the update
  6. JavaScript code should be tiny and effectively compressed
    • not only via compression but also by using techniques to minimize code size
    • 50kb as a maximum is a good target
    • there might be some heavier JavaScript for very specific things (drawing, 3D, adaptive streaming etc.), but that's not the main point here.
    • The size of the JavaScript code should not be proportional to the size of the UI
      • Basically no templates that compile to JavaScript or giant hardcoded objects (lang strings, config, hardcoded data)
  7. Styling is optimized such that both the markup and CSS are minimal
    • e.g., using cascade to avoid duplication in both HTML and CSS, possibly with post-processing
    • CSS is minified and compressed (e.g., no BEM in the final output)
  8. The app has a good system for managing different media sources.
    • e.g., Using optimal picture formats and sizes for each use case with fallback for legacy browsers
  9. It should use an efficient data format to avoid networking costs and high-memory usage
  10. Managing UI should not create excessive amounts of data in memory
    • Basically no virtual DOM and no unnecessary allocations during UI updates
    • It should offer a good way to reuse memory
  11. The Cost of updating the UI should scale linearly with the size of the update (not with the size of the UI)
    • no full rerender, no DOM diffing
  12. Minimum data duplication in memory
    • Frameworks often store the same data in both DOM and JavaScript, that's wasteful
  13. Server and Client should be well integrated, such that the communication is efficient.
    • No unnecessary data transfers
    • No N+1 requests

Conclusion 🔗

This is it for now. I probably forgot something, but it's quite a challenge already, so let's stop here for now.

As you can see, this list places quite a few design constraints on the solution (and possibly some framework). I wrote these things down because I think they are very costly to do with existing techniques, and I'm looking for a better approach.

Especially the local state management, speculative preloading, precise skeletons and optimistic updates are pretty expensive to do well. I implemented many of these things individually, but I've never been very satisfied with the result. It's always too complicated and fragile, too much work and too specific for each case. It feels like there's an abstraction waiting to be discovered, because there's a lot in common.

Part of the problem might be the current dominant paradigm, where client code is the driver of the app. I changed my mind a lot about how the apps should be architected, which I think is a part of the solution (and some frameworks already innovate here). I want to write about that next time.