Story behind the improvement:
Story behind the improvement:
Tomorrow, when you publish a Framer site, every pageβs HTML will get 10-20% smaller:
7) So now, when a site is published, it remains unoptimized only for seconds. Once bundling for top pages completes, every other page on the site starts using that bundle (+ modulepreloads, etc)!
Itβs still not as fast as optimized pages β not until the 1st visit β but itβs much faster than before.
5) 2 weeks ago, we looked at these pages and realized we can make them faster. We still canβt optimize them β itβs too costly. But we can reuse something that other pages produce β for ~free.
6) Whatβs βsomethingβ? Itβs the bundle (plus a few other things, like <link rel="modulepreload">s etc.).
4) This made optimization much faster! But this also meant a very small % of visitors would hit pages that are not bundled and not SSR-ed.
3) In October, we rewrote our optimization stack β and made it so that on publish, only the most visited pages are optimized. Less popular ones would get lazily handled after the first visit. (H/t our Piotr Krawiec, who single-handedly did the whole rewrite: www.framer.com/updates/dyn...)
1) Framer sites are fully capable React apps
2) We used to SSR every page of every site on publish. As Framer grew, in 2025, this stopped scaling (a bundling + SSR + data injection pass, which we call βoptimizationβ, would take minutes on large sites)
Last week in Framer performance: the first view of unoptimized pages is now much faster, especially on large sites:
Responded! bsky.app/profile/did:...
And learned that this doesnβt happen with every dep, only with some of them :D
(Is it a bad taste to quote-reply on bluesky btw? I havenβt posted much in a while lol, mightβve missed the memo)
This doesnβt happen with every package btw.
This happens with fast-deep-equal because itβs 1) written in CommonJS (so itβs not tree-shakable) and 2) doesnβt have `sideEffects: false` (so the bundler cannot ignore the package if its imports arenβt used).
But most packages have one of those :(
Whereas if Redux *wasnβt* bundled, the bundler wonβt even enter the file that imports fast-deep-equal. (This is thanks to `sideEffects: false` which Redux does.)
2) Make an index.js that imports and uses a completely different function:
import { compose } from "redux";
console.log(compose);
3) Bundle
4) Boom β fast-deep-equal is bundled, even though itβs not used
Yeah, Redux works great because it doesnβt import anything!
Hereβs a case when this breaks:
1) Add a random dependency in Redux thatβs used in some random function:
- esm > cjs modules (bundlers still donβt shake cjs well/at all)
- sideEffects: false ftw
- donβt bundle a library into a single file for npm. kills shakeability
spent the day shaking the tree, shook 1.5 MB
just why
Using PerformanceObserver or manual timers/logs, I guess :D
Logging into the console while itβs closed still works
(But yup, somewhat annoying π)
So turns out none of this was real
(which was pretty hard to tell because the only way to tell was to close DevTools lol)
Benchmark: codepen.io/iamakulov/pe.... Try with DevTools open vs closed β especially with call stack depth 1000.
(β1000 levels deepβ might seem like a lot, but itβs pretty realistic with Reactβs recursivelyTraversePassiveMountEffects. Thatβs how I ran into it β in TanStack Query!)
Welp, turns out itβs not real.
Just opening DevTools makes all timers 5-100Γ slower, due to the overhead of capturing stack traces. Even if you do nothing else (donβt record performance, etc)!
Guess who accidentally spent a weekend optimizing this π
(h/t @paul.irish for explaining why)
this needs to nerdsnipe someone from Chromium to investigate this :P
In the app I spotted this issue in (a typical complex React app), these setTimeouts were ~1500 calls down the `recursivelyTraversePassiveMountEffects` stack
Okay, I noticed that I cannot repro these slow setTimeouts outside of React / TanStack Query.
So I tried to profile this, and apparently setTimeout calls are much (10Γ) slower if you do them deep within a call stack (eg Reactβs recursivelyTraversePassiveMountEffects)? bsky.app/profile/did:...
setTimeout calls also become 2Γ slower if you have previously set ~750 timers (doesnβt matter whether they already fired):
Okay, so this is pretty wild: apparently, in Chromium, the deeper you are in the call stack, the slower your `setTimeout()` calls become?
gist.github.com/iamakulov/85...
hahaha, full circle :D
I might publish the impl I posted in that issue as an npm package!
(Question prompted by spending an hour to implement userland setTimeout batching :D)
Why are native setTimeout and clearTimeout calls so expensive? Like, here it takes 1.5 ms. Whatβs the technical reason itβs so slow?
Just 100-200 of those in a row (trivial if youβre mounting a bunch of React components that set timers) will easily block the page.