Sign in

Ivan Akulov

@iamakulov.com
854 followers 74 following 118 posts

Web perf engineer @ Framer. Prev. web perf consultant (Google, Appsmith, Toggl, etc). Getting React interactions 2-4x faster. GDE. He/him 🏳️‍🌈

PostsRepliesMedia
Ivan Akulov @iamakulov.com · 24/09/2026
the context is we at @framer.com have our own React framework for Framer sites. we care about perf a lot, so we inject our own smart <link rel="preload"> tags and fetchpriority hints. React’s automatic preloads mess with that, and it’s really annoying to strip them out of HTML stream :(
000
Ivan Akulov @iamakulov.com · 24/09/2026
eg: - fetchpriority=low lowers the loading priority from medium to low (in Chromium). - loading=lazy makes the image lazy (duh). today, there’s no React primitive that you can use to tell React “just keep these images as-is, don’t change anything about them”. this feels like a weird API gap.
120
Ivan Akulov @iamakulov.com · 24/09/2026
the q is: i know react 19 automatically preloads images. i also know loading="lazy" and fetchpriority="low" opt out of that. but: would you folks consider creating a more explicit out-in or opt-out for this feature? the main problem is that these attributes *have other effects*.
100
Ivan Akulov @iamakulov.com · 24/09/2026
@storyhb.com hey :) i’m a staff performance engineer at Framer. i have a quick q about automatic image preloading in React 19. (hopefully you’re the right person as you merged github.com/react/react/... – if not, sorry, just lmk who’s the best person to ask this at 🙏)
github.com
131
Ivan Akulov @iamakulov.com · 22/09/2026
in more words: 3perf.com/blog/hydrat...
3perf.com
Hidden Cost of Hydration Mismatches
A single hydration mismatch on a page can tank Largest Contentful Paint from green all the way to red
000
Ivan Akulov @iamakulov.com · 22/09/2026
it doesn’t scan elements that were already there but got larger 3) when a react hydration mismatch happens, react removes all elements on the page and replaces them with (identically looking) new ones boom. text got larger + hydration mismatch = 💥
100
Ivan Akulov @iamakulov.com · 22/09/2026
common but surprisingly rarely mentioned react perf pitfall: 1) text changes its size when fonts load (duh, you see this every day). often it gets larger 2) lcp (= google’s loading speed metric) scans for the largest element on the page. but it scans only newly added elements. it doesn’t scan--
131
Ivan Akulov @iamakulov.com · 15/07/2026
Kudos to a Framer customer for reporting a randomly freezing site. This one took two days to debug. Yes, we care about perf that much :)
0100
Ivan Akulov @iamakulov.com · 15/07/2026
> in *any* React app The critical element here is a top-level wheel event listener, which React always installs. The video above actually doesn’t use React at all, it only has that event listener installed Reported here: bugs.webkit.org/show_bug.cg...
bugs.webkit.org
319414 – [iOS, macOS] Swiping back (with NavigateEvent.intercept() + top-level wheel event listener) freezes the viewport for three seconds
1130
Ivan Akulov @iamakulov.com · 15/07/2026
Possibly one of the most impressive Safari bugs I’ve run into lately: Going back (with a swipe) in *any* React app that uses Navigation API freezes the page for three seconds. What’s fun is the main thread is not frozen, only the rendered page is.
3615
Ivan Akulov @iamakulov.com · 07/07/2026
Not widely supported enough yet (all browsers support only since late 2025 afair) But the idea was to switch to it as the main code path and use the history API only as a fallback in browsers that don’t have it
100
Ivan Akulov @iamakulov.com · 06/07/2026
TIL GTM has a History Change trigger, but this trigger doesn’t fire when you navigate via the new Navigation API :( There goes my dream of making all Framer sites use navigation.navigate() instead of history.pushState()
120
Reposted by Ivan Akulov
mattzeunert.com @mattzeunert.com · 14/04/2026
If you throttle the network in DevTools you can't always trust the metrics. @iamakulov.com explains why in his new article. 3perf.com/blog/chrome-...
3perf.com
Chrome DevTools Throttling Is Not Accurate
Chrome DevTools slows down requests, not packets. This means its throttling is often off where it matters.
121
Ivan Akulov @iamakulov.com · 14/04/2026
A few people around got recently confused by things being slow in DevTools but not in real life, so I explained why: 3perf.com/blog/chrome...
3perf.com
Chrome DevTools Throttling Is Not Accurate
Chrome DevTools slows down requests, not packets. This means its throttling is often off where it matters.
011
Ivan Akulov @iamakulov.com · 14/04/2026
Doctor: Chrome DevTools throttling is real and can hurt you Chrome DevTools throttling: [proceeds to not be real]
110
Reposted by Ivan Akulov
Mark Erikson @acemarke.dev · 19/03/2026
My Replay time-travel-based React+Redux perf reports experiments are coming together! Just nailed sourcemapped component + selector identities, and I've got accurate timing details on dispatches + renders from a Replay DevTools E2E recording! Early perf report example attached.
1103
Ivan Akulov @iamakulov.com · 27/03/2026
Something I worked on (with a few other wonderful folks) for the past few weeks :) www.framer.com/llm
010
Ivan Akulov @iamakulov.com · 18/03/2026
the chart that I feel perhaps the proudest of is this framer’s lcp (aka page loading time), across all sites, going lower and lower day by day, free for all framer customers lots of work by our infra/canvas teams, @kurtextrem.de, and yours truly <3
020
Ivan Akulov @iamakulov.com · 17/03/2026
(impressive how you’re monitoring “next.js” and responding on every social network ever, i could never)
001
Ivan Akulov @iamakulov.com · 17/03/2026
next.js (pages router) loads data for pages you navigate to, but never releases it, whoops: github.com/vercel/next...
120
Ivan Akulov @iamakulov.com · 17/03/2026
ah yes it’s my favorite “iphone safari wraps every phone number with <a> → react has a hydration mismatch → it remounts the full dom → all images on the page flash, but only on iphone” type of day
1242
Ivan Akulov @iamakulov.com · 23/02/2026
Story behind the improvement:
020
Ivan Akulov @iamakulov.com · 23/02/2026
Tomorrow, when you publish a Framer site, every page’s HTML will get 10-20% smaller:
110
Ivan Akulov @iamakulov.com · 14/02/2026
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.
000
Ivan Akulov @iamakulov.com · 14/02/2026
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.).
100
Ivan Akulov @iamakulov.com · 14/02/2026
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.
110
Ivan Akulov @iamakulov.com · 14/02/2026
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...)
framer.com
Framer Updates — Dynamic Optimization
Introducing Dynamic Optimization, an all-new way of optimizing your published websites. All sites now optimize in seconds, even large ones. And adding pages has no impact on optimization time. Pages are now optimized on first visit, and we cache the result until the next publish. Your most visited pages are pre-optimized on publish, ensuring consistent performance and full visibility into any issue. This makes Framer’s very best feature, instant publishing, even better. We’re rolling this out gradually throughout October 2025. Publishing, even faster. Only in Framer.
110
Ivan Akulov @iamakulov.com · 14/02/2026
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)
100
Ivan Akulov @iamakulov.com · 14/02/2026
Last week in Framer performance: the first view of unoptimized pages is now much faster, especially on large sites:
100
Ivan Akulov @iamakulov.com · 26/11/2025
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)
040
Ivan Akulov @iamakulov.com · 26/11/2025
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 :(
020
Ivan Akulov @iamakulov.com · 26/11/2025
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.)
110
Ivan Akulov @iamakulov.com · 26/11/2025
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
100
Ivan Akulov @iamakulov.com · 26/11/2025
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:
120
Ivan Akulov @iamakulov.com · 26/11/2025
- 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
140
Ivan Akulov @iamakulov.com · 26/11/2025
spent the day shaking the tree, shook 1.5 MB
140
Ivan Akulov @iamakulov.com · 16/11/2025
Turns out this is why! github.com/tc39/proposa...
github.com
General philosophy and vision · Issue #13 · tc39/proposal-bigint-math
Original post Spinning this out of #8 (comment) and #9 (comment). There are two dueling philosophies we could take for this proposal. “BigInts and Numbers should always be interchangeable by defaul...
100
Ivan Akulov @iamakulov.com · 16/11/2025
just why
110
Ivan Akulov @iamakulov.com · 14/11/2025
Using PerformanceObserver or manual timers/logs, I guess :D Logging into the console while it’s closed still works (But yup, somewhat annoying 🙈)
030
Ivan Akulov @iamakulov.com · 14/11/2025
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)
030
Ivan Akulov @iamakulov.com · 14/11/2025
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!)
010
Ivan Akulov @iamakulov.com · 14/11/2025
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)
3315
Ivan Akulov @iamakulov.com · 10/11/2025
this needs to nerdsnipe someone from Chromium to investigate this :P
160
Ivan Akulov @iamakulov.com · 10/11/2025
In the app I spotted this issue in (a typical complex React app), these setTimeouts were ~1500 calls down the `recursivelyTraversePassiveMountEffects` stack
020
Ivan Akulov @iamakulov.com · 10/11/2025
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:...
140
Ivan Akulov @iamakulov.com · 10/11/2025
setTimeout calls also become 2× slower if you have previously set ~750 timers (doesn’t matter whether they already fired):
140
Ivan Akulov @iamakulov.com · 10/11/2025
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...
2140
Ivan Akulov @iamakulov.com · 10/11/2025
hahaha, full circle :D
010
Ivan Akulov @iamakulov.com · 10/11/2025
I might publish the impl I posted in that issue as an npm package!
130
Ivan Akulov @iamakulov.com · 09/11/2025
(Question prompted by spending an hour to implement userland setTimeout batching :D)
120