Cut LCP, INP, and bundle size on the site you already shipped.
Engineering-led Core Web Vitals work for production sites. We profile, fix, and ship measurable improvements — usually within two weeks.
Web Performance Optimization
Most slow sites are not slow for one reason. They are slow because three or four small things compound — an unoptimized hero image, a render-blocking font, a 300KB analytics bundle, and a layout shift no one tracked. We find those, fix them, and prove the win with before/after traces.
Most slow sites are not slow for one reason. They are slow because three or four small things compound — an unoptimized hero image, a render-blocking font, a 300KB analytics bundle, and a layout shift no one tracked. We find those, fix them, and prove the win with before/after traces.
Real Web Vitals work
LCP, CLS, and INP fixes grounded in Lighthouse, WebPageTest, and field RUM data — not generic checklists.
Before / after report
Every engagement ships with a trace-backed delta: load times, bundle sizes, and Lighthouse scores on the exact pages we touched.
Production-safe changes
We work in PRs against your repo, behind feature flags where needed, with rollback notes. No surprise refactors.
The six Core Web Vitals — and exactly what we tune for each
Core Web Vitals are the metrics Google publishes, Search Console penalizes you for, and real users feel. They are not abstract scores — each one maps to a specific moment in the page lifecycle and a specific class of fix. We instrument all six in lab (Lighthouse, WebPageTest) and field (CrUX, web-vitals.js), then attack them with named techniques. Below is the breakdown — what each metric measures, the thresholds Google uses, and the exact moves we make to push you into the green.
The time from navigation start to when the largest visible element (usually a hero image, headline, or video poster) finishes rendering.
What we do (6)
- Preload the LCP image with rel=preload and fetchpriority=high
- Serve the hero in AVIF/WebP with a responsive srcset and explicit sizes
- Inline critical CSS so the first paint is not blocked on a stylesheet round-trip
- Self-host display fonts with font-display: swap and preload the WOFF2
- Eliminate render-blocking scripts in <head>; defer or async everything else
- Tune TTFB via edge caching, SSR streaming, and stale-while-revalidate
A unitless score measuring how much visible content shifts position during the page's life.
What we do (5)
- Set explicit width and height attributes on every image, video, and iframe
- Reserve space with CSS aspect-ratio for ads, embeds, and cookie banners
- Use font-display: optional or size-adjust to eliminate FOUT shift
- Render skeletons at the final element dimensions, not smaller placeholders
- Pin sticky headers and banners with transform instead of inserting into flow
The latency between a user input (tap, click, key press) and the next visual update, sampled across the entire session.
What we do (6)
- Break long tasks with scheduler.yield() and isInputPending()
- Defer non-critical hydration with React.lazy + Suspense or islands architecture
- Move parsing, sorting, and crypto off the main thread into Web Workers
- Budget third-party JS execution time and lazy-init on first interaction
- Debounce expensive React re-renders with useDeferredValue and useTransition
- Replace large event-handler closures with delegated, idempotent handlers
The time from navigation to when any text, image, or non-white pixel first renders.
What we do (5)
- Inline above-the-fold CSS and defer the rest with media=print swap
- Preconnect to required origins (fonts, CDN, analytics) in the document head
- Serve compressed HTML with Brotli and HTTP/3 from the edge
- Eliminate redirect chains and consolidate to a single canonical origin
- Stream HTML using SSR with React Suspense or Next.js app router
The duration from the user's request to the first byte of response arriving in the browser.
What we do (5)
- Cache HTML at the edge with Cloudflare, Vercel, or Fastly with SWR
- Move dynamic origins closer to users with regional functions or edge runtimes
- Add Cache-Control and Surrogate-Control headers tuned per route
- Eliminate origin redirects; collapse www, trailing-slash, and protocol hops
- Pool database connections and add Redis or KV for hot lookups
The sum of time, between FCP and TTI, when the main thread was blocked by tasks longer than 50ms.
What we do (6)
- Code-split routes and components with dynamic import() and React.lazy
- Tree-shake unused exports; audit duplicates with bundle-analyzer
- Defer or async non-critical scripts (analytics, chat, A/B, tag managers)
- Replace heavy moment.js / lodash with date-fns, dayjs, or native APIs
- Pre-compute at build time anything that does not need runtime evaluation
- Use Partytown or a worker to move third-party tags off the main thread
Two case studies — what we changed and what it moved
Ace Hardware
10 weeksWhat we changed (6)
- Hero image pipeline rebuildAVIF + WebP, srcset/sizes, preload, fetchpriority=high
- Critical CSS extractionCritters / postcss-critical-split, async stylesheet swap
- Render-blocking script removalasync/defer, requestIdleCallback, Partytown for gtag
- Layout shift fixesaspect-ratio CSS, dimension attributes, skeleton reservation
- Code-split + lazy hydrationDynamic import(), React.lazy, Suspense boundaries
- Dependency cleanupwebpack-bundle-analyzer, tree-shaking, dedupe
OpticsPlanet
Ongoing (multi-year)What we changed (6)
- Category-page hero refactorSelective hydration, AVIF/WebP, fetchpriority, DPR-aware srcset
- Product gallery srcset + lazyloading=lazy, decoding=async, srcset/sizes
- Third-party tag rationalizationPartytown, lazy-init on intent, GTM consolidation
- Redux + selector refactor for INPReselect, useDeferredValue, useTransition, scheduler.yield()
- Font subsetting + self-hostingglyphhanger subsetting, self-host WOFF2, preload, size-adjust
- Server-rendered above-the-foldSSR streaming, selective hydration, islands pattern
Numbers shown are representative of typical engagement deltas. Real lab and field traces shared during kickoff.
The toolchain we actually open every week
Performance is mostly interpretation. The tools below are industry-standard — what matters is knowing which trace answers which question, and chaining them so a Lighthouse warning leads to a flame graph that leads to a one-line PR.
Lab measurement
- Lighthouse Lab Audit
- WebPageTest Lab Tester
- PageSpeed Insights Field Audit
- Chrome DevTools Performance Browser Profiler
Real User Monitoring
- web-vitals.js RUM Library
- SpeedCurve RUM Dashboard
- Sentry Performance RUM + APM
- Datadog RUM RUM + APM
- DebugBear Continuous Monitor
Build & bundle
- webpack-bundle-analyzer Bundle Analyzer
- rollup-plugin-visualizer Bundle Analyzer
- Vite Page Bundler
- esbuild JS Transformer
- Million.js / Million Lint React Linter
Image & font pipeline
- Sharp Image Processor
- Squoosh CLI Image Compressor
- Cloudinary / Imgix Image CDN
- next/image & astro:assets Image Optimizer
- Glyphhanger / subfont Font Subsetter
Edge & CDN
- Cloudflare Edge & CDN
- Vercel Hosting + Edge
- Fastly Enterprise CDN
- Akamai Enterprise CDN
Diagnostics & adjacent
- Partytown Script Offloader
- Calibre Perf Budget
- GTmetrix Lab Audit
- Sentry Bundle Analyzer Bundle Tracker
The techniques that actually move the numbers
Performance work converges on a finite set of named techniques. We organize them by where they live in the stack — every engagement is some subset of the list below, picked by what the traces say.
Image pipeline Images are the largest single byte category on most retail and content sites, and the LCP element on 70%+ of pages. We rebuild the pipeline so format, dimensions, and priority are all correct from the build step through the CDN.
- AVIF + WebP with JPEG fallback. Sharp emits all three at build time; the CDN negotiates based on Accept headers and ships AVIF where supported.
- Responsive srcset + sizes. Per-breakpoint widths so a mobile request gets a 480px image, not a desktop one. Typical 60-80% byte cut.
- Preload + fetchpriority=high on the LCP image. A rel=preload hint with fetchpriority lifts the LCP element above other resources in the browser's queue.
- loading=lazy + decoding=async below the fold. Defers off-screen images and lets the browser decode them off the main thread.
- CDN-side transforms (Cloudinary / Imgix / next/image). Runtime resizing, format negotiation, and smart crop without bloating the repo.
- Width/height attributes on every image. Prevents the layout shift that CLS punishes — the single highest-leverage one-liner.
JavaScript The biggest performance lever on a modern site is shipping less JavaScript and running it later. We use the standard set of techniques aggressively, and instrument to prove the wins.
- Route-level code splitting. Dynamic import() at the route boundary so /checkout doesn't pay for /admin's bundle.
- React.lazy + Suspense. Component-level splitting for modals, dialogs, and below-the-fold UI; Suspense boundaries give clean fallbacks.
- Tree-shaking + dedupe. Named imports from lodash and date-fns; bundle-analyzer audits to remove duplicate copies of common libraries.
- Lazy / partial hydration. Islands architecture (Astro) or RSC (Next.js app router) so static content never ships a hydration cost.
- scheduler.yield() for long tasks. Breaks 200ms+ tasks into yield-friendly chunks so user input lands inside the INP budget.
- Web Workers + Partytown. Move parsing, sorting, and third-party tags off the main thread entirely.
CSS CSS is render-blocking by default. The win is delivering only what the first viewport needs, and getting the rest out of the critical path.
- Critical CSS inlining. Critters or postcss-critical-split inlines the above-the-fold rules so first paint doesn't wait on a round-trip.
- Async stylesheet loading. media=print swap pattern delivers the full stylesheet without blocking initial render.
- Unused CSS pruning. PurgeCSS, Tailwind's JIT, or framework-native extractors strip selectors the page never uses.
- CSS aspect-ratio reservations. Reserves space for ads, embeds, and async content — direct CLS-to-zero technique.
- container queries over media queries. Reduces layout recalculation cost on resize and reflow-heavy templates.
Fonts Custom fonts are a hidden tax on LCP and CLS. Treated correctly they cost almost nothing; treated wrong they cost 500ms and a layout shift.
- Self-host WOFF2. Drops the third-party origin handshake and lets you set your own caching headers.
- Subsetting with glyphhanger / subfont. Ships only the glyphs the site renders — typical Latin cut of 50-80%.
- font-display: swap | optional. Eliminates the invisible-text period that delays LCP on slow connections.
- size-adjust + fallback metrics. Matches the fallback font's metrics to the custom one so the swap doesn't shift layout.
- Preload + crossorigin. Hints the browser to fetch the WOFF2 in parallel with the CSS that requests it.
Caching & edge TTFB sits under every other metric. Cutting it cuts everything. We push as much of the response to the edge as the application allows.
- HTML edge caching with SWR. Cache-Control + stale-while-revalidate so users get an instant byte while the cache refills.
- ISR / on-demand revalidation. Next.js and Astro pre-render at build, revalidate on a tag or webhook — fresh content without origin pressure.
- Brotli + HTTP/3 at the edge. Smaller payloads, faster connection setup, and multiplexed streams over UDP.
- Edge runtimes (Vercel / Cloudflare Workers). Run dynamic logic 50ms from the user instead of in a single origin region.
- Early Hints (103). Lets the browser start fetching CSS and the LCP image while the origin is still building the response.
Third-party scripts Third-party tags are the most common cause of bad INP, TBT, and bundle bloat. They are also the easiest to fix because the application code doesn't have to change.
- Tag audit + consolidation. Most sites have 30-50% redundant or unused tags. Half the work is cutting the list.
- Partytown for analytics. Moves gtag, Hotjar, and segment to a Web Worker — frees 100-400ms of INP on real devices.
- Lazy-init on interaction. Chat widgets and review widgets only load on click or scroll-into-view, not on first paint.
- GTM consolidation. Single container, server-side endpoint where possible, with native deduplication of conversion events.
- Self-hosted snippets. Self-host the small snippets (Plausible, Fathom) to drop a DNS lookup and gain caching control.
Server response (TTFB) When the origin itself is the bottleneck, no amount of frontend work will rescue the page. We tune the origin and the routes that feed the LCP element.
- SSR streaming. Flush the head and above-the-fold HTML while the rest of the page is still rendering on the server.
- Database connection pooling. Reuse connections across requests so a cold function doesn't pay the handshake.
- Redis / KV for hot reads. Catalog, price, and config lookups served from memory, not the primary store.
- Redirect chain elimination. Collapse www, trailing-slash, and protocol hops into a single redirect or zero.
- Origin-shield CDN. Cloudflare Argo, Fastly Origin Shield, or Akamai SureRoute to cut origin round-trip distance.
Common questions
How long does a typical performance engagement take?
Will rankings change after we ship performance fixes?
What's the difference between Lighthouse and CrUX (lab vs field)?
What if we're on Shopify, WordPress, or a no-code platform?
Do you deliver a written report?
Can you keep monitoring after launch?
Do you work directly in our repo, or hand off a patch?
Get a free performance audit for your site
Send us a URL. We'll run lab and field traces, identify the top three wins, and send back a short written report — no pitch, no obligation.
Tell us about your project
A short message goes a long way. We'll reply within 24 hours.
Message sent
Thanks — we'll reply within 24 hours.