Incremental HTML Rendering & The Footgun Fallacy
Tags: api, html, webdev
Published: 2023-09-06 10:50:37

A response to Eric Portis' very informative article: [View Transitions Break incremental rendering](https://ericportis.com/posts/2023/view-transitions-break-incremental-rendering/).

## The issue
- Incremental HTML rendering is great
- View transitions and incremental rendering don't work well together
- Therefore, a painful tradeoff.
- What do we do?


## Who broke incremental rendering?

First of all, let's examine the title and premise of the article - incremental rendering is (1) great, (2) broken by view transitions. Who really broke incremental rendering, before view transitions were conceived?


### SPAs
Incremental rendering is indeed good and cool and helps rendering the main content faster. This has been true from the dawn of the web.

However, for better and for worse, today's web is full of SPAs - single page applications, where the default behavior is replaced and tweaked with custom JavaScript behavior, usually managed by a framework like React/Vue/... One of the attractive qualities of this way of building web apps, is that the developer building it is in control of its loading sequence rather than leaving it to heuristics that might be different across browsers. 

An operative word here is "app". For many years, there has been a wide group of websites that are seeking an "app like experience".  Without passing judgment on whether that's also cool/good, the fact is that this is extremely popular.

### Other JS techniques

Regardless of SPAs which usually control the loading sequence between pages of the same app, there are also other techniques to break incremental rendering:

- Render-blocking styles & scripts that arrive after the document is fully parsed.
- Using a service worker to artificially delay such resources until some ready state is achieved.
- Loading a page with `opacity: 0` or `display: none` initially, and changing it to `opacity: 1` or `display: block`, perhaps with an animation, when ready. 

The latter is a very popular technique in the "app like" class of web pages, for their initial page load. It's also a *wanted* technique when the page requires some JavaScript to layout itself, to avoid an ugly [flash of unstyled content](https://en.wikipedia.org/wiki/Flash_of_unstyled_content) and a resulting bad [CLS](https://web.dev/cls/). Presenting a broken layout quickly does not help with user experience, despite the speed.

### Browsers

Apart from (ab?)use of web APIs to break incremental rendering, browsers have their own heuristics in place for this. For example Chrome implements [paint holding](https://developer.chrome.com/blog/paint-holding/): a mechanism to reduce the flash of white on same-origin navigations. Safari & Firefox have their own mechanisms and heuristics to reduce flashes.

## Mid-term Conclusion

View transitions did not break incremental rendering. You did, web developers. We, browser developers, did. All of us, users, did by choosing "app-like" experiences.
To be more precise, balancing between smoothness and speed is not a simple task. That’s also the tone of Eric’s article so I think we’re actually on the same page despite me framing this as an argument for click bait purposes…

## The footgun fallacy

A common discourse when designing web APIs (and other technologies) is “this can become a footgun” - as in, people will use it the wrong way, creating more harm than good. To quote the article: “I worry that giving developers tools to explicitly block render … is going to make experiencing the web … worse”. This was also a general vibe in the discussions about the issue.

This does indeed often happen, and mitigating footgun opportunities is an important part of the API design process.

We should be careful not to miss the big picture when discussing footguns and gotchas. The experience we want to enable is realized today in all kinds of complicated ways because it’s desired. Focusing on the misuse and footgun aspects of it too much can end up generating a solution that’s more complicated than needed, potentially with its own set of intricate gotchas. 

The more powerful the tool, the more damage it can do when used in the wrong way.
Graters, scissors, drills, medicine… All of these can do harm when used in the wrong way. It’s not a reason not to have them, and of course it’s great when we can provide alternatives that are safer in that way.

The same applies to web APIs, Service workers, for example, are a whole arsenal of footguns. But they’re useful enough to make it worthwhile.

I suggest that sometimes the best way to deal with a power tool that can be a footgun is to make it look like a gun, give it a single big trigger that looks like a trigger, and put a warning label “aim before firing”.

In this particular case, for example, if we artificially scope document render blocking to view transitions only, there is a risk that people would create artificial 0-duration view transitions just for the render blocking effect. This is the shape of footguns created from artificial limitations - subtle, difficult to detect, bent backwards.

To reiterate: footgun conversations are very important and valuable. But sometimes the conclusion should be that simpler is safer, even if simpler also means more powerful. Let’s prevent footguns by putting them in plain sight.


## Back to view transitions

When transitioning inside an MPA (or in other words, performing a same-origin navigation), the developers should be able to find the balance that suits them between speed and smoothness. This is a UX choice. Smooth animations are, by definition, slower than jumping directly to the next state - which makes this *always* a tradeoff.

Eric’s article speaks about this in terms of painting slower/faster. I’d rather look at it in terms of art-directing a seamless in-app navigation.

### Art direction of a cross-document view transition
Think of a (same-origin) navigation process in this way:


#### Delayed Commit

While the new document is preparing, keep displaying a live version of the old page. During this time the old page can show some indicator that something is happening, and responds to interactions.

#### Short freeze

Capture the last frame of the old page, and keep displaying it for a short while, until the new page is ready to render or until some very short system-defined timeout has passed.

#### Transition with fallback

If everything is ready, display the finest animation you can. Otherwise (if the timeout was reached), display a fallback animation or no animation at all, based on how much of the document is ready.

By letting the developer define under what conditions the new document is considered “ready”, allowing the short “freeze” phase to take place based on those conditions, and optionally keep showing a live version of the old page for a short while (“delayed commit”), we can enable developers who want to art-direct their app-like navigation experience, and make their own design choices, without forcing them to resort to SPAs and “everything is JS”. Of course, these system-defined timeouts have to be quite short to prevent a jarring user-experience in the “freeze” phase. All of these together are an alternative to “paint slower” - freezing/render-blocking is one short (but necessary) phase in this.

### Scoping to MPAs only
Perhaps a middle ground would be to limit render-blocking to same-origin navigations only? This would support the use cases of smooth MPA navigation, without having any adverse effect on cold initial loads.

### Keeping freeze time very short
As much as the freeze phase is necessary, it’s indeed tricky as displaying a frozen image of the old page for too long would be a jarring experience. The key here is to keep this phase very short, while also enabling the other mitigations (delayed commit, fallback transitions) which don’t create this jarring effect.

## Conclusion
Incremental rendering is indeed great, especially for cold starts of a web page.
However, people already override it today in all kinds of ways when it comes to app-like experiences, the main way being adopting an SPA architecture, leaving the navigation experience to an in-document JS framework, which comes with its own arsenal of footguns.
In a world of perceived “app-like experiences”, achieving the balance between speed and smoothness calls for simple & powerful tools, clear on their intended usage, to help with the art direction of seamless navigation experiences.
Freezing the capture of the old document ([for a very short duration](https://bugs.chromium.org/p/chromium/issues/detail?id=1479081)) while the new document is settling is a necessary tool in that utility belt.
We can and should of course tweak it and design it in such a way that its intended usage is clear and that misusing it would give clear feedback.