This site started life in Webflow, and Webflow is still where I build most client work. But I wanted to know what the same design would cost to run if I controlled every byte, so I rebuilt it in Astro and kept the look identical: same fonts, same blue, same frosted portrait frame, same layout at every breakpoint.
Visually, you shouldn’t be able to spot a difference. Under the hood, almost everything changed.
Same page, two builds
I measured both versions with Lighthouse, in the same lab setup, on the same day.
| Metric | Webflow build | Astro build |
|---|---|---|
| Performance score, mobile | 30 | 93 |
| Performance score, desktop | 32 | 99 |
| Total blocking time, mobile | 144.4 s | 0.08 s |
| Page weight, desktop | 65 MB | 0.9 MB |
Those first-load figures are worse than what most visitors feel, because Lighthouse throttles the network and CPU on purpose. The gap between the two columns is real, though, and almost none of it came from Webflow itself.
Where the weight came from
When I went through the network panel, four things stood out:
- Nine project videos, all playing at once. Each project in the list had a background video set to autoplay, in WebM and MP4. Only one is ever visible in the hover card, but the browser downloaded and decoded all of them.
- A full 3D library for a flat effect. The noise pattern at the top of the hero was drawn by three.js, which shipped about 535 KB of JavaScript to render one rectangle.
- Third-party scripts on every visit. Calendly’s widget and Google Tag Manager loaded with the page, and Calendly’s stylesheet blocked rendering, even though most visitors never book a call.
- A 2K environment map for a small glass card. The footer’s glass effect loaded a high-resolution HDRI from an external host for an element 256 pixels wide.
I’d make most of these choices again on a client project, one embed at a time, because each one looks harmless on its own. Together they add up, on any platform.
The five fixes
1. Load third-party tools when someone needs them
The booking widget now loads on the first click of a “Book a call” button. Until then it costs nothing.
let loading: Promise<void> | null = null;
function loadCalendly() {
loading ??= new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://assets.calendly.com/assets/external/widget.js';
script.onload = () => resolve();
script.onerror = reject;
document.head.appendChild(script);
});
return loading;
}
Analytics got the same treatment: Tag Manager starts once the page is idle, so it never competes with the first paint.
2. Play only the video people can see
The hover card still holds every project video, but only the one in view plays, and the posters load on the first hover instead of on page load. On phones, each row’s video plays while it’s on screen and pauses when it scrolls away.
3. Draw simple effects without a library
The hero’s dithered noise is a single fragment shader. It needs a canvas and one triangle that covers it, not a 3D engine. Rewriting it in plain WebGL took the effect from 535 KB to 4 KB with an identical result.
// One triangle that covers the whole canvas.
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-1, -1, 3, -1, -1, 3]),
gl.STATIC_DRAW,
);
gl.drawArrays(gl.TRIANGLES, 0, 3);
4. Load heavy scenes near the moment they appear
The footer’s glass card does need three.js, so it still uses it. It just waits until the footer is close to the viewport:
const observer = new IntersectionObserver(([entry]) => {
if (!entry.isIntersecting) return;
observer.disconnect();
import('./glass-photo').then(({ init }) => init(canvas));
}, { rootMargin: '600px 0px' });
The environment map is now a 1K file served from the site itself. At that size on screen, nobody can tell it apart from the 2K version.
5. Let CSS handle the first paint
The page’s CSS is small, so it’s inlined into the HTML instead of loaded as a separate file, and the fonts are served from the same domain. The hero portrait’s sizes value now matches its column, so a desktop browser picks a smaller variant instead of the full 1920 px original.
Try this on your own site: open DevTools, go to the Network tab, reload, and sort by size. The top five rows usually tell the whole story.
What carries back to Webflow
You don’t need to leave Webflow to get most of this. On a Webflow build I now check:
- Background videos get a poster image and a short, compressed loop, and I keep it to one or two autoplaying per page.
- Booking widgets, chat and maps load on a click or once the page is idle, through a small custom code snippet.
- Before adding a library for an effect, I check whether CSS or a few lines of script would do.
- Webflow already generates responsive image variants, but I check the
sizesvalue, since a wrong one sends oversized images to every visitor. - A skip link, visible focus states and real links instead of clickable
divs. The rebuild scored 96 for accessibility, and you can do most of that in the Designer.
I’m not moving my client work off Webflow. For most marketing sites it’s still the right call, mostly because clients can edit content without breaking anything. What changed is my checklist: I now ask what each embed costs on first load before I add it, not after the site is live.
If you want help applying any of this to your own site, book a call.
Speed, SEO & AI search audit
Is your site slow, or missing from AI answers? I audit speed, SEO and AI visibility and fix what matters most.
