So what?
Tags: performance, webdev, react, management
Published: 2022-06-08 14:20:26

---
title: "So what?"
published: true
description: Web performance is other people.
tags: performance, webdev, react, management
series: Streets
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rmg3997h7efdosy3gf4q.jpg
---

Last time, I promised to write about “getting the benefits that SPAs enjoy, without suffering the consequences they extremely don’t enjoy”. And then [Nolan Lawson wrote basically that](https://nolanlawson.com/2022/05/21/the-balance-has-shifted-away-from-spas/), and then [the madlad did it _again_](https://nolanlawson.com/2022/05/25/more-thoughts-on-spas/). He included almost everything I would’ve:

<dl>

<dt>MPA pageloads are surprisingly tough to beat nowadays
<dd>Paint holding, streaming HTML, cross-page code caching, back/forward caching, etc.

<dt>Service Worker rendering
<dd>Also see <a href="https://alistapart.com/article/now-thats-what-i-call-service-worker/">Jeremy Wagner on why offline-first MPAs are cool</a>

<dt>In theory, MPA page transitions are Real Soon Now
<dd>In practice, Kroger.com had none and our native app barely had any, so I didn’t care

<dt>And his main point:
<dd><blockquote>If the only reason you’re using an SPA is because “it makes navigations faster,” then maybe it’s time to re-evaluate that.</blockquote>

</dl>

<small>(I don’t think he talked about <a href="https://chriscoyier.net/2022/05/04/it-doesnt-much-matter-how-cdny-your-jamstack-site-is-if-everything-important-happens-from-a-single-origin-server-edge-functions-are-probably-part-of-the-solution/">how edge rendering and MPAs are good buds</a>, but I mentioned it so [here’s ticking that box](https://www.vswong.com/articles/optimising-webapps-for-high-read-density/).)</small>

<!-- {% details Less markup-heavy version of the above, for comparison %}

<br><br>

He included almost everything I would’ve: **MPA pageloads are surprisingly tough to beat nowadays** (Paint Holding, streaming HTML, cross-page code caching, back/forward caching, etc.); **Service Worker rendering** (also see <a href="https://alistapart.com/article/now-thats-what-i-call-service-worker/">Jeremy Wagner on why offline-first MPAs are cool</a>); in theory, **MPA page transitions are Real Soon Now** (in practice, Kroger.com had none and our native app barely had any, so I didn’t care); and his main point: _“If the only reason you’re using an SPA is because ‘it makes navigations faster,’ then maybe it’s time to re-evaluate that.”_ <small>(I don’t think he talked about <a href="https://chriscoyier.net/2022/05/04/it-doesnt-much-matter-how-cdny-your-jamstack-site-is-if-everything-important-happens-from-a-single-origin-server-edge-functions-are-probably-part-of-the-solution/">how edge rendering and MPAs are good buds</a>, but I mentioned it so [here’s ticking that box](https://www.vswong.com/articles/optimising-webapps-for-high-read-density/).)</small>

{% enddetails %} -->

Since Nolan said what I would’ve (in less words!), I’ll cut to the chase: **did my opinions in this series make a meaningfully fast site?** This is the part where I put my money [where my mouth was](https://dev.to/tigt/making-the-worlds-fastest-website-and-other-mistakes-56na#the-backstory-developer-bitten-by-a-radioactive-webpagetest):

> Proving that speed mattered wasn’t enough: we also had to convince people _emotionally_. To _show_ everyone, **god dammit**, how much better our site would be if it were fast.

The best way to get humans to feel something is to have them experience it. Is our website painful on the phones we sell? Time to inflict some pain.

## The demo

I planned to demonstrate the importance of speed at our monthly product meeting. It went a little something like this:

1. <p>Buy enough <a href="https://dev.to/tigt/making-the-worlds-fastest-website-and-other-mistakes-56na#some-sort-of-higherlevel-goal">Poblano phones</a> for attendees.</p>
2. <p>On those phones and a throttled connection, try using Kroger.com:</p>
   1. Log in
   2. Search for “eggs”
   3. Add some to cart
   4. Try to check out
3. <p>Repeat those steps on the demo.</p>
4. <p>Note how performance is the bedrock feature: without it, no other features exist.</p>

<figure>

![A MacBook Pro with “Who was Jake Wary?” scrawled on it, plugged into a dock for power and ethernet. Fanned out nearby are cheap Android phones not connected to anything, along with an Alcatel flip phone.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vmq03vwvpdoejp981xxq.jpg)

<figcaption><p>Near the laptop with the horrible pun are 10 of the original 15 demo phones. (The KaiOS flip phone helped stop me from overspecializing for Chrome or the Poblano VLE5 specs.)</figcaption>
</figure>

A nice thing about targeting wimpy phones is that the demo hardware cost me relatively little. Each Poblano was ≈$35, and a sale at the time knocked some down to $25.

<!-- <small>(If you want to have a similar demo, I wrote down <a href="https://dev.to/tigt/setting-up-a-wireless-real-device-demo-with-network-throttling-3d2e-temp-slug-3977196?preview=15524e366df17c4bbd730bc638444ace8d29155a0aaaba3345b4a4c6438497551c2db72520433bb6623a97484800ddaf9d14d70d5f910ded61504475">the setup’s technical details</a>, including accurately throttling each phone’s connection, fairly comparing with a local fork of the prod site, and a checklist of easily-forgettable steps.)</small> -->

## How fast was it?

Sadly, I can’t give you a demo, so this video will have to suffice:

<figure>
<video src="https://assets.codepen.io/183091/all-5-at-once.handbraked.mp4" controls muted></video>

<figcaption><p>The browser UI differs because each video was recorded at different times. (Also, guess walmart.com’s framework.)

{% details Text description of video %}

<p>Me racing to get the demo, kroger.com, Kroger’s native app, amazon.com, and walmart.com all to the start of checkout as fast as possible. In order of how long each took:

<ol>
<li>The demo, in 20 seconds
<li>amazon.com, in 59 seconds
<li>The Kroger native app, in 1 minute 21 seconds
<li>walmart.com, in 2 minutes 14 seconds
<li>kroger.com, in 3 minutes 44 seconds
</ol>

<p>If you want to improve dev.to’s video accessibility, consider voting for <a href="https://github.com/forem/forem/discussions/16853">my feature request for the <code>&lt;track&gt;</code> element</a>.

{% enddetails %}
</figcaption>
</figure>

For a bit, our CDN contact got it semi-public on the real Internet. I was beyond excited to see this in [@AmeliaBR](https://twitter.com/AmeliasBrain)’s Firefox devtools:

![A Gantt chart showing Request Timing: Blocked 63 ms, DNS Resolution 19 ms, Connecting 17 ms, TLS Setup 24 ms, Sending 0 ms, Waiting 166 ms, and Receiving 1 ms. Summarized as Started at 63 ms, Downloaded at 293 ms.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/erxri2q1xf2r8wkvm8qm.png)

That’s Cincinnati, Ohio &rarr; Edmonton, Canada. [293 milliseconds ain’t bad for a network response](https://web.dev/rail/), but I was so happy because I knew we could get much faster…

- About <a href="https://wondernetwork.com/pings/Edmonton/Cincinnati">50–100ms was from geographical distance</a>, which can be improved by edge rendering/caching/etc.
- <a href="https://community.pivotal.io/s/article/Gorouter-buffering-behavior-for-HTTP-requests-and-responses?language=en_US">PCF’s gorouters have a 50ms delay.</a> Luckily, we were dropping PCF.
- [40ms from Nagle’s algorithm](https://rachelbythebay.com/w/2020/10/14/lag/), maybe even 80ms from both Node.js and the reverse proxy. [This is what `TCP_NODELAY` is for.](https://www.extrahop.com/company/blog/2016/tcp-nodelay-nagle-quickack-best-practices/)
- Tweaked gzip/brotli compression, like [their buffer sizes](https://www.euccas.me/zlib/#zlib_io_buffer) and [flushing behavior](https://zlib.net/zlib_how.html#:~:text=to%20control%20data%20latency%20on%20a%20link%20with%20compressed%20data)
- [Lower-latency HTTPS configuration](https://hpbn.co/transport-layer-security-tls/#optimizing-for-tls), such as smaller TLS record sizes

Let’s say that averages out to 200ms in the real world. Based on the numbers in the first post, that’s ***$40 million/year based on kroger.com’s 1.2 TTFB today***. Or, [~5% of company profit at the time](https://ir.kroger.com/CorporateProfile/press-releases/press-release/2021/Kroger-Delivers-Strong-Fourth-Quarter-and-Fiscal-Year-2020-Results/default.aspx). (The actual number would probably be higher. With a difference this large, latency→revenue stops being linear.)

## So… how’d it go?

Or [as Jason Grigsby put it](https://twitter.com/grigs/status/1509307824924336129):

> The burning questions are related to how it performed and what the organization thought about it? How much was adopted? Etc.

### What did the organization think of it?

The immediate reaction exceeded even my most indulgent expectations. Only the sternest Dad Voice in the room could get enough quiet to finish the presentation. Important people stood up to say they’d like to see more bottom-up initiative like it. VIPs who didn’t attend requested demos. Even some developers who disagreed with me on React and web performance admitted they were intrigued.

Which was nice, but kroger.com was still butt-slow. As far as how to _learn_ anything from the demo, I think these were the options:

1. Adapt new principles to existing code
2. Rewrite (incremental or not)
3. Separate MVP

#### Adapt new principles to kroger.com’s existing code?

Naturally, folks asked how to get our current React SSR architecture to be fast like the demo. And that’s fine! Why ***not*** React? Why not compromise and improve the existing site?

We tried it. Developers toiled in the Webpack mines for smaller bundles. We dropped IE11 to polyfill less. We changed the footer to static HTML. After months of effort, we shrank our JS bundle by ≈10%.

One month later, we were back where we started.

Does that mean fast websites are too hard in React? C’mon, that’s a clickbait question impossible to answer. **But it _was_ evidence that we as a company couldn’t handle ongoing development in a React SPA architecture without constant site speed casualties.** Maybe it was for management reasons, or education reasons, but after this cycle repeated a few times, a fair conclusion was we couldn’t hack it. When every new feature adds client-side JS, it felt like we were set up to lose before we even started. (Try telling a business that each new feature must replace an existing one. See how far you get.)

At some point, I was asked to write a cost/benefit analysis for the MPA architecture that made the demo fast, but in React. It’s long enough I can’t repeat it here, so instead I’ll do a Classic Internet Move™: _gloss a nuanced topic into controversial points._

##### Reasons not to use React for Multi-Page Apps

<dl>
<dt>React server-renders HTML slower than many other frameworks/languages
<dd><p>If you’re server rendering much more frequently, even small differences add up. <a href="https://github.com/marko-js/isomorphic-ui-benchmarks">And the differences aren’t that small.</a>

<dt>React is kind of bad at page loads
<dd><p><a href="https://bundlephobia.com/scan-results?packages=react,react-dom"><code>react</code> + <code>react-dom</code> are bigger</a> than many frameworks, and its growth trendline is disheartening.
<dd><p>In theory, React pages can be fast. <a href="https://timkadlec.com/remembers/2020-04-21-the-cost-of-javascript-frameworks/">In practice, they rarely are.</a>
<dd><p><a href="https://svelte.dev/blog/virtual-dom-is-pure-overhead#it-s-not-just-the-diffing-though">VDOM is not the architecture you’d design if you wanted fast loads.</a>
<dd><p>Its rehydration annoys users, does lots of work at the worst possible time, and is fragile and hard to reason about. Do you want those risks on <em>each page?</em>{% details ℹ️ Okay, I feel like I have to back this one up, at least. %}

<blockquote><p>Performance metrics collected from real websites using SSR rehydration indicate its use should be heavily discouraged. Ultimately, the reason comes down to User Experience: it's extremely easy to end up leaving users in an “uncanny valley”.

<p>—&thinsp;<a href="https://web.dev/rendering-on-the-web/#a-rehydration-problem:-one-app-for-the-price-of-two">Rendering on the Web § A Rehydration Problem: One App for the Price of Two</a></blockquote>

<p>The Virtual DOM approach inflicts a lot of overhead at page load:

<ol>
<li>Render the entire component tree
<li>Read back the existing DOM
<li>Diff the two
<li>Render the reconciled component tree
</ol>

<p>That’s a lot of unnecessary work if you’re going to show something mostly-identical to the initial <code>text/html</code> response!

<p>Forget the performance for a second. Even rehydrating <em>correctly</em> in React is tricky, so using it for an MPA risks breakage on every page:

<ul>
<li><a href="http://farmdev.com/thoughts/107/why-server-side-rendering-in-react-is-so-hard/">Why Server Side Rendering In React Is So Hard</a>
<li><a href="https://joshwcomeau.com/react/the-perils-of-rehydration/">The Perils of Rehydration</a>
<li><a href="https://blog.jakoblind.no/case-study-ssr-react/">Case study of SSR with React in a large e-commerce app</a>
<li><a href="https://blog.logrocket.com/fixing-gatsbys-rehydration-issue/">Fixing Gatsby’s rehydration issue</a>
<li><a href="https://github.com/gatsbyjs/gatsby/issues/17914">gatsbyjs#17914: [Discussion] Gatsby, React & Hydration</a>
<li><a href="https://github.com/facebook/react/issues?q=is%3Aissue+label%3A"Component%3A+Server+Rendering"">React bugs for “Server Rendering”</a>
</ul>

<p>No, really, skim those links. The nature of their problems is more important than the specifics.

{% enddetails %} 

<dt>React fights the multi-page mental model
<dd><p>It prefers JS properties to HTML attributes (you know, the <code>class</code> vs. <code>className</code> thing). That’s not a dealbreaker, but it’s symptomatic.
<dd><p>Server-side React and its ecosystem strive to pretend they’re in a browser. Differences between server and browser renders are considered isomorphic failures that should be fixed.

</dl>

React promises upcoming ways to address these problems, but testing, benching, and speculating on them would be a _whole other post_. (They also extremely didn’t exist two years ago.) I’m not thrilled about how React’s upcoming streaming and partial hydration seem to be implemented — I should test for due diligence, but a separate HTTP connection for a not-quite-JSON stream doesn’t seem like it would play nice during page load. 

Taking it back to my goals, does Facebook even use React for its rural/low-spec/poorly-connected customers? There is one data point of the almost-no-JS <a href="https://mbasic.facebook.com/">mbasic.facebook.com</a>.

#### Rewrite kroger.com, incrementally or not?

Software rewrites are the Forever Joke. Developers say _this_ will be the last rewrite, because finally we know how to do it _right_. Businesses, meanwhile, knowingly estimate how long each codebase will last based on how wrong the developers were in the past.

Therefore, the natural question: should our next inevitable rewrite be Marko?

I was able to pitch my approach vs. another for internal R&D. I can’t publish specifics, but I did make this inscrutable poster for it:

<figure>

![The Greatest Fight in History! Jared ‘The’ Fox’s Big Bean BBQ & BAR “No, I’m not giving you a quote for that” Presents: Rumpus in the Grumpus Proof-of-Concept Speed Showdown! Marko f.k.a. ‘Clipper’ vs. ‘Bridge’ a.k.a. poc-1! Monday 9AM, Direct from ringside at Atrium Two secret basement fight club, 221 East 4th St Cincinnati, Free Country USA. All seats reserved starting $20/GB. Ringside is where it’s at; No VoDs; No Microsoft Teams recording.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dml7v67dhpgkuzkxfh5e.png)

<figcaption><p>And because I’m an incorrigible web developer, <a href="https://codepen.io/tigt/pen/JjKeygR/b467e6cf52e52f27436cca19afdd629d">I made it with HTML & CSS</a>.</figcaption>
</figure>

That bakeoff’s official conclusion: “performance is an application concern, not the platform’s fault”. It was decided to target Developer Experience™ for the long-term, not site speed.

I was secretly relieved: **how likely will a new architecture _actually_ be faster if it’s put through the same people, processes, and culture as the last architecture?**

With the grand big-bang rewrite successfully avoided, we could instead try small incremental improvements — speed A/B tests. If successful, that’s reason enough to try further improvements, and if _those_ were successful…

The simplest thing that could possibly work seemed to be streaming static asset `<script>` and `<link>` elements before the rest of the HTML. We’d rewrite the outer scaffolding HTML in Marko, then embed React into the dynamic parts of the page. Here’s a simplified example of what I mean:

```html
import {
  renderReactRoot,
  fetchDataDependencies
} from './react-app'

<!doctype html>
<html lang="en-us">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <for|{ url }| of=input.webpackStaticAssets>
    <if(url.endsWith('.js')>
      <script defer src=url></script>
    </if>
    <if(url.endsWith('.css')>
      <link rel="stylesheet" href=url>
    </if>
  </for>

  <PageMetadata ...input.request />
</head>
<body>
  <await(fetchDataDependencies(input.request, input.response)>
    <@then|data|>
      $!{renderReactRoot(data)}
    </@then>
  </await>
</body>
</html>
```

This had a number of improvements:

- <p>Browsers could download and parse our static assets while the server waited on dynamic data and React SSR.</p>
- <p>Since Marko only serializes components with <code>state</code>, the outer HTML didn’t add to our JS bundle. (This had more impact than the above example suggests; our HTML scaffolding was more complicated because it was a Real Codebase.)</p>
- <p>If successful, we could rewrite components from the outside-in, shrinking the bundle with each step.</p>
- <p>Marko also paid for itself with more efficient SSR and smaller HTML output (quote stripping, tag omission, etc.), so we didn’t regress server metrics unless we wanted to.</p>

This almost worked! But we were thwarted by our Redux code. Our Reducers ‘n’ Friends contained enough redirect/page metadata/analytics/business logic that assumed the entire page would be sent all at once, where any code could walk back up the DOM at its leisure and change previously-generated HTML… like the `<head>`.

We tried to get dev time to overcome this problem, since we’d have to make Redux stream-friendly in a React 18 world anyway. Unfortunately, [Redux and its ecosystem weren’t designed with streaming in mind](https://github.com/reduxjs/redux-toolkit/discussions/1820), so assigning enough dev time to overcome those obstacles was deemed “not product-led enough”.

#### Launch a separate, faster version of kroger.com?

While the “make React do this” attempts and the Streaming A/B test were, you know, fine, they weren’t my favorite options. I favored launching a separate low-spec site with respectful redirects — let’s call it `https://kroger.but.fast/`. I liked this approach because…

- Minimum time it took for real people to benefit from a significant speedup
- Helped with the culture paradox: your existing culture gave you the current site. Pushing a new approach through that culture will change your current culture or the result, and the likelihood of which depends on how many people it has to go through. A small team with its own goals can incubate its own culture to achieve those goals.
- If it’s a big enough success, it can run on its own results while accruing features, until the question “should we swap over?” becomes an obvious yes/no.

### How much was adopted?

Well… that’s a long story.

The Performance team got rolled into the Web Platform team. That had good intentions, but in retrospect a platform team’s high-urgency deploys, monitoring, and incident responses inevitably crowd out important-but-low-urgency speed improvement work.

Many folks were also taken with the idea of a separate faster site. They volunteered skills and time to estimate the budget, set up CI/CD, and other favors. Their effort, kindness, and optimism amazed me. It seemed inevitable that _something_ would happen — at least, we’d get a concrete rejection that could inform what we tried next.

The good news: something did happen.

The bad news: it was the USA Spring 2020 lockdown.

After the initial shock, I realized I was in a unique position:

- <p>COVID-19 made it extremely dangerous to enter supermarkets.
- <p>The pandemic was disproportionately hurting blue-collar jobs, high-risk folks, and the homeless.
- <p>I had a proof-of-concept where even cheap and/or badly-connected devices can quickly browse, buy, and order groceries online.

People won’t _stop_ buying food or medicine, even with stay-at-home orders. If we had a website that let even the poorest shop without stepping in our stores, it would ***save lives***. Even if they could only browse, it would still cut down on in-store time.

With a certainty of purpose I’ve never felt before or since, I threw myself into making a `kroger.but.fast` [MVP](https://en.wikipedia.org/wiki/Minimum_viable_product). I knew it was asking for burnout, but I also knew I’d regret any halfheartedness for the rest of my life — it would have been morally wrong not to try.

We had the demo running in a prod bucket, agonizingly almost-public, only one secret login away. We tried to get _anyone_ internally to use it to buy groceries.

I’m not sure anyone bothered.

I don’t know what exactly happened. My experience was very similar to [Zack Argyle’s with Pinterest Lite](https://listen.casted.us/public/12/Mobile-Matters-748aa039/3b92ffbe), without the happy ending. (It took him 5 years, so maybe I’m just impatient.) I was a contractor, not a “real employee”, so I wasn’t privy to internal decisions — this also meant I couldn’t hear why any of the proposals sent up the chain got lost or rejected.

<!-- The weird part is, explicitly lower-income food stores all have slow React websites. Here’s some WebPageTest traces: [ALDI](https://www.webpagetest.org/result/220601_BiDcHT_81H/), [Dollar General](https://www.webpagetest.org/result/220601_BiDcGA_813/), [Dollar Tree](https://www.webpagetest.org/result/220601_BiDc9V_816/), [Family Dollar](https://www.webpagetest.org/result/220601_AiDcSN_81C/), [99 Cents Only](https://www.webpagetest.org/result/220601_BiDcZP_81E/), [Five Below](https://www.webpagetest.org/result/220601_BiDcM9_81A/)…
-->

Once it filtered through the grapevine that Bridge maybe was competing for resources with a project like this… that was when I decided I was doing nothing but speedrunning hypertension by staying.

## When bad things happen to fast code

On the one hand, the complete lack of real change is obvious. The demo intentionally rejected much of our design, development, and even management decisions to get the speed it needed. Some sort of skunkworks to insulate from ambient organizational pressures is often the _only_ way a drastic improvement like this can work, and it’s hard getting clearance for that.

Another reason: is that to make a drastic improvement on an existing product, there’s an inherent paradox: a lot of folks’ jobs depend on that product, and you can’t get someone to believe something they’re paid not to believe. Especially when the existing architecture was sold as faster than the even-more-previous one. (And isn’t that always the case?)

It took me a while to understand how people could be personally enthusiastic, but professionally could do nothing. One thing that helped was [Quotes from _Moral Mazes_](https://www.lesswrong.com/posts/45mNHCMaZgsvfDXbw/quotes-from-moral-mazes). Or, if you want a link less likely to depress you, I was trying to make [a Level 4 project happen in an org that could charitably be described as Level 0.5](https://infrequently.org/2022/05/performance-management-maturity/).

## But enough about me. What about you?

Maybe you’re making a website that needs to be fast. **The first thing you gotta do is get _real_ hardware that represents your users.** [Set the right benchmarks for the people you serve.](https://adhoc.team/2022/04/12/setting-right-benchmarks-for-site-speed-in-government/) Your technology choices must be informed on that or you’re just posturing.

If you’re targeting cheap phones, though, I can tell you what I’d look at today.

For the closest performance to my demo, try [Marko](https://markojs.com/). ~~Yes, I’m paid to work on Marko now~~, [EDIT: not anymore] but what technology would better match my demo’s speed than the _same_ technology? (Specifically, I used [`@marko/rollup`](https://www.npmjs.com/package/@marko/rollup).)

But, it’s gauche to only recommend my employer’s thing. What else, what else… If your site doesn’t need JS to work, then absolutely go for a static site. But for something with even sprinkles of interactivity like e-commerce — well, there’s a reason my demo didn’t run JAMstack.

My checklist of requirements are…

- Streaming HTML. (See [part #2](https://dev.to/tigt/the-weirdly-obscure-art-of-streamed-html-4gc2) for why.)
- Minimum framework JS — at least half of `react` + `react-dom`.
- The ability to only hydrate _some_ components, so your users only download JavaScript that _actually_ provides dynamic functionality.
- Can render in CDN edge servers. This unfortunately is hard to do for languages other than JavaScript, unless you do something like [Fly.io’s One Weird Trick](https://fly.io/docs/getting-started/multi-region-databases/).

[Solid](https://github.com/solidjs/solid-start) is the closest runner-up to Marko; the only requirement it lacks is partial hydration.

Svelte doesn’t stream, or have partial hydration, but tackles the too-much-app-JS problem via its culture discouraging it. If Svelte implemented streaming HTML, I’d recommend it. [Maybe someday.](https://github.com/sveltejs/svelte/issues/958)

If Preact had partial hydration and streaming, I’d recommend it too; even though Preact’s goals don’t always match mine, I can’t argue with Jason Miller’s consistent results. Preact probably will have equivalents of React’s streaming and Server Components, right?

Remix is _almost_ a recommend; its philosophies are 🧑‍🍳💋. [Its progressive enhancement approach is exactly what I want](https://blog.jim-nielsen.com/2022/joining-remix/), as of React 18 it can stream HTML, and they’re doing invaluable work [successfully _convincing_ React devs that those things are important](https://remix.run/blog/not-another-framework). This kind of stuff has me shaking my fists in agreement:

> Wouldn’t it be great, if we could just move all of that code out of the browser and onto the server? Isn’t it annoying to have to write a serverless function any time you need to talk to a database or hit an API that needs your private key? (yes it is). These are the sorts of things React Server Components promise to do for us, and we can definitely look forward to that for data loading, but they don’t do anything for mutations and it’d be cool to move that code out of the browser as well.
> 
> —&thinsp;[Remix: The Yang to React’s Yin](https://kentcdodds.com/blog/remix-the-yang-to-react-s-yin)

<!-- -->

> We’ve learned that fetching in components is the quickest way to the slowest UX (not to mention all the content layout shift that usually follows).
>
> It’s not just the UX that suffers either. The developer experience gets complex with all the context plumbing, global state management solutions (that are often little more than a client-side cache of server-side state), and every component with data needing to own its own loading, error, and success states.
>
> —&thinsp;[Remixing React Router](https://remix.run/blog/remixing-react-router)

Really, the only thing I don’t like about Remix is… React. Check this perf trace:

<figure>

![Zoomed-in portion of CPU % graph from 2.0 seconds to 3.4 seconds. From 2.2 to 2.6, the main thread is saturated by purple and yellow (style calc and script execution, respectively), then an unknown gray factor saturates the main thread from roughly 2.9 to 3.3. The red long task indicator is not happy about either of those timespans.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/94ys3ijbb72zqi2r5wyy.png)

<figcaption><p>Sample from <a href="https://www.webpagetest.org/result/220322_AiDc5R_EB/1/details/cached/#waterfall_view_step1">this <code>remix-ecommerce.fly.dev WebPageTest trace</a></figcaption>
</figure>

Sure, the main thread’s only blocked for 0.8 seconds total, but I don’t want to do that to users on _every_ page navigation. That’s a good argument for why Remix progressively enhances to client-side navigation… [but I’ve already made my case on that](https://dev.to/tigt/routing-im-not-smart-enough-for-a-spa-5hki).

Ideally, Remix would let you use other frameworks, and I’d shove Marko in there. [They’ve discussed the possibility](https://github.com/remix-run/remix/issues/425), so who knows?