дэн Profile picture
May 26 19 tweets 9 min read
I’m thankful to Evan for pointing out perf bugs on beta.reactjs.org. I planned to look later but got nerdsniped. My screenshots are ~averages from ten runs I just did for both sites. (Prone to some variance, of course!) I want to share what I fixed. reactjs.org  First Contentful Paint 1.0 s Time to Interactivvuejs.org  First Contentful Paint 1.2 s Time to Interactive https://beta.reactjs.org (10 runs)  2.4s 70ms 2.2s 110ms 2.4
The main mistake from the bundle size perspective was that we had an “utils” file, it got a heavy dependency (CodeMirror linter logic for sandboxes), but it was imported from the main entry point. The fix for that was to split the utils file into several. github.com/reactjs/reactj… Screenshot showing size reducing by half
While doing this, I noticed we load the CodeMirror linter logic eagerly on the pages that *do* have sandboxes, but that’s also a bit too early. I wanted to defer that a bit compared to loading other code. Doesn’t affect the homepage but is a nice fix too. github.com/reactjs/reactj…
While doing *that*, I noticed that our ESLint sandbox integration bundles all built-in ESLint rules. But actually, we only want to bundle the React ESLint rules which our sandboxes will run automatically. So I fixed that too (also doesn’t affect homepage). github.com/reactjs/reactj…
Then I noticed that Next.js has not yet switched to bundling for modern browsers by default. Not a huge deal, but it bloats the generated code a bit. Until this is the default behavior, I flipped an experimental flag to enable it. github.com/reactjs/reactj…
Running that version on webpagetest (webpagetest.org/video/compare.…) shows there is a layout shift due to the custom font loading too late. Very bad! The font was hosted from another domain, and connecting to it took too much time. Serving from same domain fixed the shift in most cases. layout shiftbefore: font loads too lateafter: loads right after htmlafter screenshot: no shift
Finally, you might have noticed the Total Blocking Time has gone down quite a bit. Actually, I didn’t do much. In React 18, wrapping some UI in <Suspense> makes its hydration non-blocking. I added a couple <Suspense> tags, and React 18 did the rest. github.com/reactjs/reactj… Before: TBT is 190msAfter: TBT is 70ms
React 18 hydrates synchronously down to the closest <Suspense> boundaries. Remaining hydration is non-blocking and happens in chunks. This is automatic: I added some <Suspense> boundaries, and hydrating them became non-blocking! That’s why TBT went down. One long task taking 133ms with throttlingA smaller 71ms task that lets the hydration happen sooner, f
Now, of course you could say... But why run this code on the client in the first place? Isn’t hydrating something that’s mostly static kinda useless? Things are usually not 100% static, but a mix. One hybrid solution is Astro-like “islands”: splitting your code in two paradigms.
We don’t find solutions that split by paradigm very satisfying. So the approach we are working on is Server Components (which can run during the build, btw). Server Components let you stay within a single paradigm, but make some code server-only. Hydrating those parts is instant.
Instead of solving performance in the order of [ship less code] -> [optimize remaining client code], we are solving it in the order [optimize client code assuming there’s a lot] -> [ship less code]. Same eventual result but there is a higher bar to make client optimizations good.
In the end, the code that isn’t needed on the client shouldn’t be shipped to the client. Hydrating should be instant for static parts and only take a bit of time for interactive parts. But if a slowdown does happen, your <Suspense> boundaries will keep it non-blocking. 😍
As <Suspense> becomes widely adopted (after data fetching integrations), this perf improvement will come from simply adding a loading state somewhere in your code. It’s essentially a better default for hydration behavior. For more about React 18, check out
There’ll be more to optimize. Especially on sandbox-heavy pages. Like Evan pointed out, the INP metric (web.dev/inp/) looks sad. I doubt it’s because React is slow. :) I have one guess for a possible cause. If you want to help, here’s an idea: github.com/reactjs/reactj… INP showing 497ms
anyway,,, goodnight captured at may 26, 2022, 4:58am gmt+1at may 26, 2022, 4:58am gmt4:58am
*typo in the first tweet — should say “from nine runs”. the calculations were correct though
btw i think this is a fair point :)
One last thing. The benefits of non-blocking hydration in React 18 are much more obvious on a page with a *lot* of content. It doesn’t matter how much content there is! The browser becomes (and remains) responsive after the outer shell hydrates. Concurrent rendering is nice. one long task takes 319ms (6x throttling)first task takes 128ms, then the rest are chunked (6x thrott
Of course, it would be even better if this only included the interactive components and skipped the static parts... But we already talked about this. We want mechanisms to do both so that you don’t have to choose.

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with дэн

дэн Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @dan_abramov

May 23
my favorite “bundle analyzer” tool is taking <script> tag contents, putting it though Prettier, copy pasting into my editor, and scrolling through it. if i don’t understand where the code is from, i guess by names or search for identifiers in the libraries i’m using.
this lets me notice a whole class of issues (“wtf is this thing”, “why is it compiled inefficiently”, “why does this run so early”, “wait wasn’t this code supposed to be dead”) that higher-level friendlier tools miss
for react, we have a build mode that applies all the usual minification techniques *except* it leaves all variable names intact. this is amazing and something i miss a lot when dealing with analyzing Next.js/CRA bundles. a flag to build with readable output would be dope
Read 4 tweets
May 9
The difference between having Strict Mode on and off is between “eager bugs” and “on-demand bugs”. Strict Mode immediately forces you to handle the edge cases. This includes bugs you won’t hit in prod today but definitely will as your logic changes. I’ve seen this many times.
This means it’s less of a technical and more or a cultural question. Do you prefer to hit all the edge cases in the beginning and use them to guide (and sometimes rethink) the approach? Or do you prefer to hit them as the code evolves and different cases appear in prod?
I emphatize with “we need to ship this week” mindset. I say it’s fine to just disable the Strict Mode and reenable it back when you have some time to do foundational work. But I also think people undervalue a tool that magically surfaces entire classes of future prod bugs today.
Read 11 tweets
May 5
So, this is a real pain point and I want to show how we think the useEvent RFC can solve it. (Small thread)
*Every* value inside useEffect is considered a dependency. The effect body is fully “reactive” — whenever any value changes, we re-fire the effect. This is to ensure that the result of the effect is always consistent with the latest props/state.

But you don’t always want that...
Consider this example. You want to log every page visit and attach current user name to the log. You only want to re-run it when the visited URL changes.

However, the linter nags you to include the current user name as a dependency. That can be a problem! function Page({ route, curr...
Read 12 tweets
May 4
We’ve posted an RFC for useEvent. We suspect it might have been the missing piece in the original Hooks release. It lets you define an event handler that “sees” fresh props/state but has a stable identity. Would love to hear feedback! github.com/reactjs/rfcs/p…
i probably won’t be able to follow all the discussion on twitter so please comment on the RFC too!
anyway, it’s nice to have a physical escape key
Read 4 tweets
Apr 29
ok let’s do this. like this tweet and i’ll subtweet u in replies
you’ve basically put me out of my job and i’m thankful for it
your fanperson energy about a certain band is contagious and i like to see it
Read 54 tweets
Apr 29
i think we’re all going to die and disappear. i don’t believe in literal afterlife, stateful reincarnation, etc. but! i have a metaphysical mortality cope that i want to share. it doesn’t mean much (i said metaphysical!) but soothes my mind a little bit.
it goes like this. there is a feeling of “nowness” that you feel right now. it changes with every moment. it’s like you’re flowing through time. in that sense once you run out of those moments, you’re gone. no more experiencing. sounds sad so far…
now suppose that each of those moments “existed” in some metaphysical moment-space. like a collection of all frames in a movie. each frame “exists” regardless of where the playhead is. each frame “exists” regardless of whether the file is over, or whether it is playing.
Read 9 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us on Twitter!

:(