Skip to content

A performance audit report, run on our own site

Sample report, run on our own site

We didn’t want to show a performance audit of a made-up company with made-up numbers, so we audited convoke.software. The measurements are real and dated, good and bad, and every finding has its evidence, cause, fix, effort and expected gain.

Sample report, run on our own site. This is not a made-up client. We measured the live convoke.software on 31 October 2026, from 8:09pm to 8:58pm Pacific time (03:09 to 03:58 UTC on 1 November), with Lighthouse, traces, response headers and a production build. Every number below comes from those runs, and you can download every run as a CSV. It’s shortened: four pages instead of up to five, and no walkthrough call.

Summary

A website performance audit measures how fast your key pages load and respond, finds what slows them down, and orders the fixes by what they’ll buy you. It covers Core Web Vitals in the lab and the field, the JavaScript, images and fonts each page ships, caching, and the rendering strategy.

convoke.software is fast in the lab. On Lighthouse’s default settings every page scores 100 for performance on mobile and desktop, and every one of the 160 runs scored 100 for accessibility, best practices and SEO. Under a stricter test that actually slows the connection, mobile LCP is 2.07 to 2.16 seconds, inside Google’s 2.5 second “good” line but with little to spare, and most of that time is spent waiting for a stylesheet.

The costs are in what each page downloads after it paints. The Google Analytics script is preloaded as if the page needed it to render, and on a slow phone it adds 66 to 90 ms to LCP and 1.9 to 3.5 seconds to Time to Interactive. The animated hero downloads about 240 KB of 3D graphics code on every page with the hero band (all but contact and privacy), more than the rest of the site’s JavaScript put together. Neither shows in a score of 100.

We couldn’t get field data from real visitors for this audit; the field data section says why and what would provide it.

Method

Site
https://convoke.software (production)
Date
31 October 2026, Pacific time
Tool
Lighthouse 13.6.0, Chrome 156, headless
Machine
Apple M3 Max, macOS 26.6, Vancouver
Runs
160: 5 per page, device, throttling and set-up
Build
Next.js 14.2.21, production build of the live code

Pages. The home page, the white label service page, the blog post with the most search impressions (Claude Design Skills) and the performance audit page. Between them they cover the site’s templates: home, service, article and audit.

Devices. Mobile is Lighthouse’s default: an emulated Moto G Power, 412 pixels wide, on a 150 ms round trip and 1.6 Mbps connection, with the CPU slowed four times. Simulated runs model that profile directly; applied runs use Lighthouse’s browser-level version of it, a 562.5 ms request latency and 1.47 Mbps download. Desktop is Lighthouse’s desktop preset: 40 ms and 10 Mbps under simulation, no CPU slowdown, and no network throttling in applied runs.

Two ways of throttling. Lighthouse’s default, the one PageSpeed Insights uses, loads the page at full speed and then simulates how long it would have taken on the slow connection. Applied throttling actually slows the connection and the CPU in the browser while the page loads. We ran both: simulated, because it’s the number most people will compare against, and applied, because it’s closer to a real slow phone and because the simulation got the analytics comparison wrong (below).

Analytics. Every run blocked Google Analytics’ collection requests, so no test visit reached the site’s analytics. We checked the network log of every run: no collection request got a response. To measure what the analytics script itself costs, each page ran twice: once with googletagmanager.com blocked too, so no analytics at all, and once with gtag.js allowed to load. The difference between the two is the cost of the script.

Why the analytics cost uses applied throttling. Under simulation, the pages without analytics came out 0.62 to 0.77 seconds slower to first paint than the pages with it, which can’t be right. The most likely reason is that Lighthouse’s simulator still counts the blocked gtag.js preload as a request to a new host, with its connection set-up, while in the real run it failed at once. Applied throttling has no such step, so its difference is the one we report.

Limits. This machine is fast: Lighthouse rated its CPU at about 3,700 to 3,900, well above the range its documentation gives for a high-end desktop. Slowed four times it is still quicker than a mid-range phone, so CPU-bound numbers (TBT, long tasks, TTI) are on the kind side. Applied throttling also shares the connection evenly between requests, without the priorities a real HTTP/2 connection would use, so it probably overstates how long the stylesheet waits in finding 3. Lab tests can’t measure INP, which needs real interactions; TBT is its lab stand-in.

Also checked with curl at the same time: response headers, compression, redirects, robots.txt, sitemap.xml, llms.txt, the structured data on each page, and the JavaScript chunks the live pages load, matched to a next build of the code in production.

Lab results

Each cell is the median of five runs, with the range underneath. These runs load gtag.js, as a visitor’s browser does, with only the collection requests blocked. FCP is First Contentful Paint, LCP Largest Contentful Paint, TBT Total Blocking Time, CLS Cumulative Layout Shift and TTI Time to Interactive, which Lighthouse still reports but no longer scores.

Lighthouse defaults (simulated throttling)

Lab results, simulated throttling
PageDevicePerformanceFCPLCPTBTCLSSpeed IndexTTI
HomeMobile100100 to 1000.95 s0.93 to 1.050.95 s0.93 to 1.055 ms4 to 70.0000.000 to 0.0001.16 s1.02 to 1.362.62 s2.54 to 2.74
HomeDesktop100100 to 1000.29 s0.28 to 0.380.29 s0.28 to 0.380 ms0 to 00.0000.000 to 0.0000.51 s0.45 to 0.700.29 s0.28 to 0.38
White label serviceMobile100100 to 1000.92 s0.92 to 0.970.92 s0.92 to 0.974 ms4 to 60.0000.000 to 0.0000.92 s0.92 to 1.082.54 s2.48 to 2.58
White label serviceDesktop100100 to 1000.28 s0.27 to 0.310.28 s0.27 to 0.310 ms0 to 00.0000.000 to 0.0000.38 s0.36 to 0.430.28 s0.27 to 0.31
Blog postMobile100100 to 1000.94 s0.93 to 1.000.94 s0.93 to 1.008 ms4 to 160.0000.000 to 0.0000.95 s0.93 to 1.002.75 s2.68 to 3.43
Blog postDesktop100100 to 1000.27 s0.26 to 0.380.49 s0.46 to 0.640 ms0 to 00.0000.000 to 0.0000.38 s0.34 to 0.460.49 s0.46 to 0.64
Performance auditMobile100100 to 1000.95 s0.93 to 1.020.95 s0.93 to 1.026 ms4 to 80.0000.000 to 0.0000.98 s0.93 to 1.022.51 s2.46 to 2.57
Performance auditDesktop100100 to 1000.27 s0.27 to 0.350.27 s0.27 to 0.350 ms0 to 00.0000.000 to 0.0000.42 s0.35 to 0.460.27 s0.27 to 0.35

Applied throttling

Lab results, applied throttling
PageDevicePerformanceFCPLCPTBTCLSSpeed IndexTTI
HomeMobile9595 to 952.16 s2.14 to 2.242.16 s2.14 to 2.2416 ms10 to 200.0050.000 to 0.0053.67 s3.64 to 3.735.57 s5.48 to 5.82
HomeDesktop100100 to 1000.25 s0.19 to 0.310.25 s0.19 to 0.310 ms0 to 00.0000.000 to 0.0000.67 s0.63 to 0.750.25 s0.19 to 0.31
White label serviceMobile9796 to 972.07 s2.05 to 2.152.07 s2.05 to 2.1512 ms7 to 390.0000.000 to 0.0002.68 s2.66 to 2.773.95 s3.84 to 5.56
White label serviceDesktop100100 to 1000.23 s0.20 to 0.330.23 s0.20 to 0.330 ms0 to 00.0000.000 to 0.0000.48 s0.44 to 0.560.23 s0.20 to 0.33
Blog postMobile9696 to 962.16 s2.15 to 2.172.16 s2.15 to 2.179 ms8 to 440.0000.000 to 0.0002.67 s2.65 to 2.695.26 s5.19 to 5.66
Blog postDesktop100100 to 1000.23 s0.21 to 0.300.23 s0.21 to 0.300 ms0 to 00.0000.000 to 0.0000.51 s0.43 to 0.580.23 s0.21 to 0.30
Performance auditMobile9796 to 972.11 s2.04 to 2.222.11 s2.04 to 2.2212 ms8 to 150.0000.000 to 0.0002.74 s2.67 to 2.853.91 s3.84 to 4.02
Performance auditDesktop100100 to 1000.28 s0.23 to 0.400.28 s0.23 to 0.400 ms0 to 00.0000.000 to 0.0000.51 s0.45 to 0.620.28 s0.23 to 0.40

Desktop is barely touched by throttling, so the two methods agree there. On mobile they don’t: applied throttling puts LCP at about 2.1 seconds where the simulation says about 0.95. The simulation estimates the slow connection from a fast load; applied throttling makes every request wait.

What the analytics script costs

Applied throttling, mobile, medians of five runs each. TTI and TBT differences are with the script minus without it.

The cost of the analytics script on mobile
PageLCP without analyticsLCP with gtag.jsLCP differenceTTI differenceTBT difference
Home2.08 s2.16 s+80 ms+3.49 s+16 ms
White label service1.99 s2.07 s+74 ms+1.91 s+12 ms
Blog post2.09 s2.16 s+66 ms+3.17 s+9 ms
Performance audit2.02 s2.11 s+90 ms+1.89 s+12 ms

The script also adds 181,817 bytes to every page. On desktop the difference was inside the normal spread between runs (from 138 ms faster to 15 ms slower), so we read it as no measurable effect.

Where the mobile LCP time goes

From the trace and network log of the first applied run on each page. On mobile the LCP element is text on every page, so there is nothing to load: once the HTML has arrived, the whole wait is render delay.

LCP phase breakdown on mobile
PageLCP elementTime to first byteLoad delayLoad timeRender delayLCP
HomeThe intro paragraph under the heading (text)630 msNone (text)None (text)1,522 ms2,152 ms
Blog postThe standfirst paragraph under the title (text)631 msNone (text)None (text)1,517 ms2,148 ms

Of the render delay, 1,422 ms on the home page and 1,438 ms on the blog post is the main stylesheet arriving; the text paints 80 to 100 ms after it lands, before the web fonts do. Time to first byte here includes the emulated connection; on the unthrottled loads the server answered in about 90 to 170 ms. On desktop the LCP element is the home page’s heading and the blog post’s first screenshot, which is preloaded and painted within about 100 ms of the HTML.

Long tasks on mobile

Main-thread tasks over 50 ms across the five applied runs, with the analytics script loaded.

Long tasks on mobile
PageBelongs toTasks in 5 runsEach
Homegtag.js754 to 64 ms
HomeNot attributed to a script250 to 52 ms
Homethree.js (hero animation)250 to 54 ms
Blog postgtag.js850 to 72 ms
Blog postthree.js (hero animation)153 ms

None is long enough to matter on its own: TBT never went above 44 ms. With analytics blocked, the gtag.js tasks disappear. On the home page the two three.js tasks went too, leaving only the two not attributed to a script; on the blog post the three.js task stayed.

JavaScript

First Load JS per route from next build of the code in production. The chunk names match the ones the live pages load. The shared 87.4 kB is two framework chunks: React DOM (53.6 kB) and the Next.js runtime (31.6 kB).

First Load JS per route
RouteFirst Load JS
/158 kB
/services/[slug]149 kB
/audits/[slug]149 kB
/blog/claude-design-skills154 kB
Shared by every route87.4 kB

First Load JS leaves out anything loaded after the page starts. Two things are, and between them they are bigger than everything above:

Scripts loaded after the page starts
WhatFilesCompressedUncompressedWhere
Hero animation: three.js (two chunks), React Three Fiber, the dot sea component4240,867 bytes882,407 bytesEvery measured page
Google Analytics (gtag.js)1181,817 bytes542,766 bytesEvery page

Field data

Field data was not available to this audit. Lab tests are one browser on one connection; field data is what real visitors experienced. It’s the version Google uses, and the only way to measure INP.

It would come from two places. The Chrome UX Report (CrUX) publishes the 75th percentile of LCP, INP and CLS from real Chrome users over the last 28 days, for the whole site and for busy pages, once a site has enough traffic; it’s read through the CrUX API, which needs a key we didn’t have, or BigQuery. Search Console’s Core Web Vitals report shows the same data grouped by similar pages, to anyone with access to the property.

We didn’t use PageSpeed Insights to fill the gap: its lab run would have loaded the site with analytics running. We also didn’t check whether CrUX holds data for convoke.software, so we make no claim either way. In a client audit, field data is the first thing we ask for, and the report says plainly when a site is too quiet to have any.

Five findings

Ordered by impact. Each has the evidence, the cause, the fix, the effort and the expected gain. Expected gains are Lighthouse’s own estimates or our measured difference, labelled as estimates; where neither exists, we say so.

1The analytics script is preloaded as if it were needed to render

Evidence

Every page’s <head> preloads Google’s gtag.js: 181,817 bytes compressed, 542,766 uncompressed. Chrome requests it at High priority, the same as the fonts and above every one of the site’s own scripts, which go at Low. On a throttled phone it finishes at about 3.7 to 5.4 seconds, then runs one or two long tasks of 50 to 72 ms. Lighthouse marks 73 KiB of it as unused during load. Measured against the same pages with no analytics, it adds 66 to 90 ms to mobile LCP, 1.9 to 3.5 seconds to TTI and 9 to 16 ms to TBT.

Cause

The site loads analytics with the GoogleAnalytics component from @next/third-parties, which uses Next.js’s script component with its default afterInteractive strategy. In the App Router that strategy also adds a preload link, so the browser starts fetching the script straight away, alongside the stylesheet the first paint is waiting for.

Fix

Load gtag.js with Next.js’s lazyOnload strategy, which adds no preload and waits until the browser is idle. The GoogleAnalytics component has no strategy option, so this means replacing it with two next/script tags: the gtag.js loader and the inline config. Keep the consent default script running before them, then check in GA4’s real-time view that page views still arrive.

Effort:
Small.
Expected gain:
Estimate, from the measured difference: up to 66 to 90 ms of mobile LCP. The TTI cost shrinks but doesn’t vanish, because the script still loads, just later; not estimated.

2The hero animation downloads a 3D engine on every page with the hero band

Evidence

Every measured page, on mobile and desktop, loads four chunks for the animated dot sea in the hero: three.js in two chunks, React Three Fiber, and the component itself. Together they are 240,867 bytes compressed and 882,407 uncompressed, more than half of the site’s own JavaScript on each page. Lighthouse marks 107 KiB of the two three.js chunks as unused. On a throttled phone they start after the page has painted and finish at about 5.3 to 5.8 seconds, and three.js ran a long task of 50 to 54 ms in some runs.

Cause

The dot sea is drawn with three.js, a general 3D engine, through React Three Fiber. It is already loaded the right way round: after the page renders, and not at all for visitors who prefer reduced motion or whose browser lacks WebGL2. So it doesn’t touch LCP. It costs data, CPU and battery on every visit to those pages for a moving background.

Fix

In order of effort: skip the animation on small screens and when the browser reports Save-Data or a slow connection, keeping the plain dark surface; or redraw the dots with a single WebGL2 shader, or a 2D canvas, with no 3D engine. The first is a few lines in the check that already decides whether to load it.

Effort:
Medium.
Expected gain:
Lighthouse estimates 107 KiB of unused JavaScript in the three.js chunks; time saved not estimated.

3Text on mobile waits for the stylesheets

Evidence

On mobile the LCP element is text on all four pages. In the applied traces the HTML arrives at about 630 ms and both stylesheets are requested at about 640 ms on three of the pages (801 and 813 ms on the audit page), and the main one (13.7 KB compressed) doesn’t finish until between 1,970 and 2,136 ms, because it shares the slow connection with two preloaded fonts, gtag.js and the site’s scripts. The text paints 80 to 100 ms later. Lighthouse flags both stylesheets as render-blocking on every page.

Cause

Next.js links the site’s CSS as ordinary stylesheets in the <head>, and a browser won’t paint until they arrive. That is the normal set-up, and on a fast connection it costs one short round trip. On a slow one it is most of the LCP.

Fix

Inline the CSS the first screen needs into the HTML and load the rest without blocking. All of the site’s CSS is 15.7 KB compressed, so inlining it whole is also an option, at the cost of sending it with every page instead of caching it once. Either way it needs trying on this Next.js 14 App Router set-up before committing to it; fixing finding 1 also frees bandwidth for the stylesheet.

Effort:
Medium.
Expected gain:
Lighthouse estimates 130 to 160 ms off mobile FCP and LCP with applied throttling, or 110 to 170 ms with simulated throttling, on the white label, blog and audit pages; none estimated for the home page.

4Framer Motion ships with every page for fade-ins

Evidence

The chunk that holds Framer Motion loads on every measured page: 40,456 bytes compressed, 121,055 uncompressed. Lighthouse marks 23 KiB of it as unused during load.

Cause

The hero components and the cookie banner import Framer Motion’s full motion component for entrance animations, which brings its whole feature set.

Fix

Use Framer Motion’s LazyMotion with the smaller domAnimation features and the m component, so only what the fade-ins use loads up front; or move simple fade-ins to CSS.

Effort:
Small to medium.
Expected gain:
Lighthouse estimates 23 KiB of unused JavaScript; time saved not estimated.

5The blog post: one oversized image and an unsubsetted font

Evidence

Lighthouse finds 15 KiB to save on one of the post’s screenshots, the Neubrutalism library view. The post also loads Geist Mono, the site’s monospace font: 71,596 bytes, the published variable font file with every weight in it and no subsetting, where the two text fonts are 29,904 and 37,792 byte subsets.

Cause

The screenshot is served larger than it’s displayed. Geist Mono is loaded from the variable font file as published, because it isn’t in the Google Fonts list this version of Next.js ships with, so nothing subsets it.

Fix

Give the screenshot a sizes value that matches its displayed width. Subset Geist Mono to Latin characters, and to the weights the site uses if they’re few.

Effort:
Small.
Expected gain:
Lighthouse estimates 15 KiB on the image. The font saving is not estimated.

Checked and fine

  • Accessibility, best practices and SEO: 100 on all 160 runs.
  • Layout shift: CLS 0.005 or less on every run, against Google’s “good” line of 0.1.
  • Main thread: TBT 44 ms at most, in any run.
  • Server and caching. Pages come from Vercel’s edge cache over HTTP/2, compressed with Brotli when the browser asks for it (the home page is 18,800 bytes with Brotli, 24,071 with gzip). Static files are cached for a year and marked immutable. HTML is revalidated on every request, so a deploy shows straight away.
  • One host. http:// and www. both redirect to https://convoke.software in one permanent hop, and canonicals match.
  • Fonts. Both text fonts use font-display: swap with size-matched fallbacks, so text paints before they arrive and doesn’t move when they do. Their Latin subsets are preloaded.
  • Images. Served as WebP through Next.js’s image component, with a set of sizes to choose from. The blog post’s first screenshot is preloaded at high priority; the rest load lazily.
  • Structured data. The JSON-LD on all four pages parses and has the properties Google requires: Organization and WebSite everywhere, BreadcrumbList on inner pages, BlogPosting with author and dates, Service and FAQPage where they belong. Google only shows FAQ rich results for authoritative government and health sites, so the FAQ markup won’t earn one, but it isn’t an error.
  • Legacy JavaScript. Lighthouse flags 12 KiB of polyfills. They are Next.js 14’s own polyfill module, 1,380 bytes before compression, so Lighthouse’s estimate overstates it. Not worth a change on its own.
  • Crawl files. robots.txt, sitemap.xml and llms.txt are all served.

Next steps

Fix list
OrderFixEffortExpected gain
1Load gtag.js with lazyOnload, without the preloadSmallUp to 66 to 90 ms of mobile LCP (estimate from the measured difference)
2Stop the hero animation loading its 3D engine on phones, or rebuild it without oneMedium107 KiB of unused JavaScript (Lighthouse estimate)
3Inline the CSS the first screen needsMedium130 to 160 ms (applied throttling) or 110 to 170 ms (simulated) of mobile FCP and LCP on three pages (Lighthouse estimates)
4Load Framer Motion through LazyMotion, or move fade-ins to CSSSmall to medium23 KiB of unused JavaScript (Lighthouse estimate)
5Resize one blog image; subset Geist MonoSmall15 KiB on the image (Lighthouse estimate); font not estimated
  • Each fix gets re-measured with the same runs once it ships, so the before and after sit side by side.
  • Field data from CrUX or Search Console, when it’s available, decides whether the mobile LCP margin needs more than fixes 1 and 3.
  • A full audit adds a fifth template, field data per template, caching and the rendering strategy page by page, infrastructure costs, and a 30-minute walkthrough call.

For five findings like these on your own site, written up by a person, send us a URL for the free site snapshot. For the full report, see the performance audit.

Have something to build?

Tell us what you're working on. We'll tell you how we'd approach it.

We reply within one working day.