🎉 Super glad to announce that I &
@addyosmani
made a web performance guide for webpack!
— Making front-end smaller
— Improving caching
— Monitoring it to stay fast
Live at Web Fundamentals:
Not to pick on Spotify in particular, but it's interesting how slow some "desktop apps" to do things like switch between playlists that are already loaded on my machine. Feels more like a "website" than an "app"
Ever read an article that says “setTimeout(..., 0) fires not in 0 ms but in 4”? This is no longer true – at least not in Chrome and Safari, and not since a few months ago.
But getting there was a bit of a bumpy road.
This weekend’s project: debugging a performance issue where running
div.style.transform = 'translateX(100px)'
was triggering a style recalculation for 15K nodes 😨
(Guess why)
Favorite hobby: rewriting third-party React libraries in pure CSS to avoid forced style recalcs.
Today’s example: react-slick (a 15kB image gallery) was causing multiple style recalcs for a client. Replaced with a 20-line almost-pure-CSS implementation with scroll snapping:
If you ever looked inside a non-minified bundle, you might’ve seen a lot of automatically generated comments that look like this:
/*#__PURE__*/
Even wondered what they are for? Here’s a short thread 🧵
⚛️ React hydration is one of the first things I look at whenever I’m optimizing loading speed of a React app. It’s super expensive – because it has to render every single component on the page.
But how do you measure how long it takes? And why is it so expensive? ↓
🔥 We’ve updated the WebFundamentals’ “Web performance with webpack” guide for webpack 4!
✂ Now mentioning the production mode, the new code splitting approach, and replacing some plugins to work with the new webpack.
➡ Check it out →
You might’ve seen this picture in the past. It shows the steps that happen in the browser whenever it needs to update the page:
JavaScript → Style → Layout → Paint → Composite
But what’s actually happening there is not that simple (and is pretty fascinating). Let’s dive in!
Life update: after ~6 years of doing web perf consulting, I turned the page and joined
@framer
. I’ll be leading site performance efforts there, together with a team of amazing engineers 🖤
TIL Safari DevTools have a “Type Profiler”, which shows types the engine inferred for each JS variable ↓
Not sure why or when this can be useful in practice, but looks like a cool gimmick!
Finally published the article I’ve been working on for the past 3 weeks!
📝 Case study: Analyzing Notion app performance
Or how to make a React app load ~30% faster – just by tuning some configs and delaying some scripts.
→
My favorite HTTP/2 feature is Connection Coalescing.
In HTTP2, normally, a browser opens a new connection for every new domain it sees. (So if your site loads via www. but serves images from img., that’d be 2 connections.)
But new connections are costly! So browsers do a trick.
A superb explainer for the `font-display` CSS attribute by
@notwaldorf
:
(`font-display` allows to display the text while the font for it is still loading)
awesome-webpack-perf just got updated! ✨
Added a bunch of new webpack tools for web perf, including
— esbuild-webpack-plugin
— zero-runtime CSS-in-JS libs
— and more analysis tools
🎉 The new webpack 4.6 now supports prefetching chunks!
Use this when you have a dynamic import and you know that you’ll need the imported file soon. The browser will prefetch the file in the background!
Today’s learning: Cloudflare can break React hydration in the most funny way possible :D
1. Make a React site that renders `<p>My email: ivan
@example
.com</p>`
2. Put it behind Cloudflare
3. Hydration mismatch!
🚀 Launching... a curated list of webpack tools for web performance!
A whole bunch of plugins and loaders for:
— Compressing JS, CSS, images and fonts
— Service workers & PWA
— Preloading & <script async/defer>
— Bundle analysis
Okay, let’s talk about bundle duplicates.
A common issue in JS bundles is duplicated dependencies. E.g., lodash, core-js, some polyfill libs are frequently bundled multiple times.
Here’s how to detect and solve this issue: a thread ⬇
📝 Did you know HTML has as much as 5 different tags to preload something? Here’s a detailed deep-dive into each of them.
🔬 How they look
⚙ What they do
🤷♀️ And when to use each one
🌅 <img> and background-image download images differently.
— <img> tag fetches an image as soon as HTML arrives.
— background-image fetches it much later, only when all CSS files download (even if you specify style="background-image: …" straight in HTML).
Why is there a delay?
CSS tip: if you use `overflow: scroll`, it’s likely you actually want `overflow: auto`.
“overflow: scroll” means “*always* show scrollbars”. You won’t see the difference on a Mac because it autohides all scrollbars. But on Windows, it’d look like this:
OK, seriously, the Zen of Python is one of the best things created in software development (and it doesn’t apply to Python only, of course).
I keep and keep and keep returning to it when I’m deciding which one of a few similar interfaces or implementations to choose.
@rauschma
I like having a git-ignored .env file with secret keys in the root of the repo.
1) It’s compatible with
2) There’re common helpers like the dotenv package
3) It’s easy to create a .env.example file which describes the config variables
A performance check I like to do whenever I work with a server-rendered React site is:
Open DevTools → ⌘⇧P → “Disable JavaScript” → reload the page.
If the page looks very different with JS disabled, the site’s got performance issues.
PSA: always set the Cache-Control (or Expires) header on your responses.
If you don’t, browsers will cache responses based on their internal heuristics. And this could result in an unexpected behavior.
🔥 The “Web Performance 101” talk transcript is finally out!
94 slides about:
👑 Why performance matters
🚀 How to optimize JS, CSS, HTTP stuff, images and fonts
⚒ Tools that help you better understand you app’s perf
→
3) So now, when you’re calling `setTimeout(0)`, what actually happens is:
• if it’s a fresh `setTimeout(0)` call, it will run immediately (in all browsers)
• if it’s a nested `setTimeout(0)` call, it will run in 4 ms after 5 (Firefox), 10 (Safari), or 15 (Chrome) nested calls
Tip: the easiest way to debug your JS performance is the Web Vitals extension, with console logging enabled.
• Click around the page
• See console logs for how long every click took + where the browser spent most of the time
⚛️ My “React 18 Concurrency, Explained” talk slides are now fully out with text transcription!
The talk is an under-the-hood look at how startTransition() & new <Suspense> hydration work in React 18, plus their drawbacks. Some of trivia & full slides below ↓
Super glad to announce that I’m now a Google Developer Expert in web technologies!
Hope that would allow me to spread the message about web performance and how it’s important even further.
1) The HTML spec tells that `setTimeout()` calls nested 5+ levels deep must be throttled to 4 ms. This prevents poorly written sites from over-consuming CPU.
(Note the word “nested” – unnested `setTimeout()`s were never intended to be throttled! But we’ll get to that in a bit.)
Ever had a case when you type something in a React app, and it reacts super super slowly?
One of the reasons that may happen is unnecessary rerenders. Here’s how to find and fix them (a thread) ⬇️
Shout-out to (by
@csswizardry
and
@RyanTownsend
) which is a super easy way to get a “slow” file.
It also added support for images and font files recently!
Turns out if you can save 70 Mb of RAM if you launch a Node.js app with `node index.js` (instead of `yarn start`). Useful if you run lots of apps in a memory-sensitive environment.
E.g., this is the process tree of my container. Yarn uses almost as much memory as the app itself!
Okay, it’s Friday – let’s have some web perf fun!
1) Run a webpack build with webpack-bundle-analyzer
2) Take a screenshot and reply to this tweet
3) I’ll dig and see what to improve!
2) There’s not one but several React renders.
This is likely an example of the Cascading Rerenders antipattern (gosh I should write an article on this):
- you await on several promises
- the first promise resolves → you render
- the second promise resolves → you rerender
- …
Tip from
@tkadlec
, hot off the press:
When you’re profiling a website performance, type `-has-response-header:Content-Encoding` into the network filter. This will find you all resources that are not compressed with gzip/Brotli (= are a good low hanging fruit to optimize)
Ever had cases in webpack where
import { Grid, Row } from 'react-bootstrap';
creates a bundle larger than
import Grid from 'react-bootstrap/es/Grid.js';
import Row from 'react-bootstrap/es/Row.js';
?
I explained why this happens:
OK, now a serious question.
Why is there no <link rel="stylesheet" async> for non-blocking stylesheets?
Having this would help to load critical CSS first, and then fetch the remaining styles without blocking the rendering. Right now, you have to use JS hacks to achieve this.
Woah, Chrome 85 will start marking fast sites with the “Fast” badge:
The badge will be based on Core Web Vitals metrics. (The metrics data will, apparently, come from the Chrome UX report.)
Spotify is an Electron app, meaning we can use Chrome DevTools to profile it. To enable Chrome DevTools in Spotify, I install `spicetify` and run `spicetify enable-devtools`.
Yay!
🎉 The latest alpha of webpack 4 now minifies everything by default in the production mode!
This means that for simple projects, you won’t need to configure anything – just run `webpack --mode production`, and you’ll have a production-ready bundle!
***
What surprised me though is that the UglifyJsPlugin is not enabled in the production mode by default. I was seriously expecting that the production mode would be like “I enable it, specify a couple of loaders, and it just works.” Turns out, nope, I still have to add plugins.
React Profiler > console.log(). Here’s how to use React Profiler to find out why a specific component rerenders:
1) Go to DevTools Settings and enable this setting:
Fun fact: if your webpack bundle has 1K modules, `__webpack_require__()` alone would take 25% of its initial execution time.
(`s` is the minified `__webpack_require__`. Trust me)
Tip: If you’re writing a piece of code that’s logging an error, *always* log the error as a separate `console.log` argument.
✅ This is good:
console.log('Error:', e)
❌ This is not:
console.log(`Error: ${e.message}`)
Here’s a bunch of common patterns & why they’re wrong ⬇
Gah I’m really excited about <Suspense> and hydration in React 18.
With React 16-17, `.hydrate()` is often the most expensive JS call in the whole app. You start hydrating the app – and the page freezes for, like, a second:
One of the reasons your Lighthouse score might be bad is third parties.
Many (not all, though!) third parties execute a lot of JavaScript. This blocks the main thread & worsens your JS-related Lighthouse metrics (TTI/TBT).
Here’re several ways to optimize them – a thread ⬇️
A few months ago, I had to build a full-stack app having only front-end experience.
So now, I’m writing a high-level backend intro to help you if you appear in the same situation.
Here’s part 1 covering Node.js and popular backend libraries →
Just discovered Fontsource by
@vercel
– and woah it’s an amazing project:
Fontsource makes self-hosting Google Fonts much easier. You just `npm install` a font and import is as a regular module:
import "@fontsource/open-sans";
This is so great.
And this adds up.
For one of clients I worked with, replacing the `./components/index.js` file with direct imports dropped around 200 KBs off the main bundle (~10% of its size).
1) It makes code splitting ineffective.
When you do
import { Button } from './components'
you’re importing not only Button but the whole ‘./components’ file. Which means you’re *bundling* the whole file – with all the components it exports.
“But shouldn’t tree shaking help?”
Tree shaking still works as expected here – it still drops all unused components (“unused” as in “unused in the application”).
Tree shaking just doesn’t apply to components that are used in the app but are unused in the current chunk.
2) Have a static site? Serving fonts from your own server?
Subset these fonts to characters that are actually used on your pages. This will help to load fonts faster.
You can easily do this at the build time using
— or
—
2) How I find what to preload:
— Go to DevTools and run a performance recording of how the page loads
— Scroll through the Network section and find stuff that blocks page rendering but gets downloaded too late (= too much down in Network section). That’s usually CSS or JS
1/2
@kentcdodds
— There’s also a 307 redirect. It works like 302 but preserves the request method.
E.g. if you do POST /url, and /url returns “302, go to /url2”, the browser will do GET /url2 next.
But if you do POST /url, and it returns “307, go to /url2”, the browser will do POST /url2.
TIL Netlify sends all its responses with
Cache-Control: max-age=0, must-revalidate
(which, effectively, disables caching). And it’s a feature, not a bug:
I’ve been experimenting with tracking app responsiveness and talking to product teams, on and off, over the last couple years.
So here’s a guide on how to set up your own React runtime perf monitoring:
1) Spotify uses React.
This `t.unstable_runWithPriority` function in the flamechart is a key sign of React. Whenever you see it, it’s React running.
Which means it’s not surprising that switching playlists is slow! With all my love for React, it defaults into slow performance.
(“Everything” = “all descendants of the changed node”. Although in this case, this was pretty much the whole page.)
6) And that’s how you get a 15K-node recalc from changing a single inline CSS `transform` rule.
🧊 How to do partial hydration in React, 2021 edition ↓
“How to do what?” Partial hydration (also “lazy hydration”) lets you skip hydrating components that don’t have any logic. This helps Next.js/Gatsby/SSR sites to initialise faster – and improves Total Blocking Time.
A pattern I see pretty often is when a single file re-exports stuff from lots of other files.
If your project has a `components/index.js` file which re-exports all your components (and does nothing else), that’s one example.
This is bad for performance – for two reasons.
I have two favorite absurdist programming talks. They’re funny and not that serious but have made me think for quite a while:
— The Birth and Death of JavaScript
— The Future of Programming