From 300bc2ef44ec9b61d0f8ee3d8ed69912c695acce Mon Sep 17 00:00:00 2001 From: rf Date: Sat, 27 Jun 2026 14:56:42 +1000 Subject: [PATCH 1/5] feat(cwv): add good core web vitals --- .github/workflows/cwv-stats-monthly.yml | 4 +- packages/cwv-stats/cwv-stats.json | 58 ------ packages/cwv-stats/src/cwv/cwv.ts | 34 +++- .../cwv-stats/src/frameworks/frameworks.ts | 9 +- .../cwv-stats/src/httparchive/httparchive.ts | 4 +- packages/docs/src/components/ChartTabs.astro | 41 +++- .../src/components/ComparisonBarChart.astro | 4 +- .../src/components/CoreWebVitalChart.astro | 35 ++++ .../src/components/CoreWebVitalsCharts.astro | 20 ++ .../src/components/CoreWebVitalsTable.astro | 25 +++ packages/docs/src/content.config.ts | 36 +++- packages/docs/src/content/cwv/cwv-stats.json | 176 ++++++++++++++++++ packages/docs/src/lib/collections.ts | 32 ++++ packages/docs/src/pages/run-time.astro | 5 + 14 files changed, 406 insertions(+), 77 deletions(-) delete mode 100644 packages/cwv-stats/cwv-stats.json create mode 100644 packages/docs/src/components/CoreWebVitalChart.astro create mode 100644 packages/docs/src/components/CoreWebVitalsCharts.astro create mode 100644 packages/docs/src/components/CoreWebVitalsTable.astro create mode 100644 packages/docs/src/content/cwv/cwv-stats.json diff --git a/.github/workflows/cwv-stats-monthly.yml b/.github/workflows/cwv-stats-monthly.yml index 5b72108c..1168eac8 100644 --- a/.github/workflows/cwv-stats-monthly.yml +++ b/.github/workflows/cwv-stats-monthly.yml @@ -5,7 +5,7 @@ on: # of the following month therefore we run the crawl on the 7th to give a full weeks buffer after the crawl finishes. # https://har.fyi/guides/release-cycle/#running-the-crawl schedule: - - cron: '0 0 7 * *' # At 00:00 on day-of-month 7 (UTC) + - cron: "0 0 7 * *" # At 00:00 on day-of-month 7 (UTC) workflow_dispatch: # Allow manual triggering jobs: @@ -26,7 +26,7 @@ jobs: run: | container_id=$(docker run -d cwv-stats) docker wait "$container_id" - docker cp "$container_id:/app/cwv-stats.json" ./packages/cwv-stats/cwv-stats.json + docker cp "$container_id:/app/cwv-stats.json" ./packages/docs/src/content/cwv/cwv-stats.json cat ./packages/cwv-stats/cwv-stats.json docker rm "$container_id" diff --git a/packages/cwv-stats/cwv-stats.json b/packages/cwv-stats/cwv-stats.json deleted file mode 100644 index e64926cd..00000000 --- a/packages/cwv-stats/cwv-stats.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "framework": "Svelte", - "date": "2026-04-01", - "overall": { - "mobile": 0.5257320161204038, - "desktop": 0.6419379360899193 - }, - "lcp": { - "mobile": 0.6814389308900151, - "desktop": 0.8373465373397545 - }, - "cls": { - "mobile": 0.813545042884055, - "desktop": 0.7491705988870825 - }, - "fcp": { - "mobile": 0.6394810765447064, - "desktop": 0.8271154276979034 - }, - "ttfb": { - "mobile": 0.5857041465673832, - "desktop": 0.7353218166228582 - }, - "inp": { - "mobile": 0.7613325312077004, - "desktop": 0.9738081902942048 - } - }, - { - "framework": "Next.js", - "date": "2026-04-01", - "overall": { - "mobile": 0.32837317168530766, - "desktop": 0.5218013684903662 - }, - "lcp": { - "mobile": 0.5239420471475185, - "desktop": 0.728655890907357 - }, - "cls": { - "mobile": 0.6885431949314789, - "desktop": 0.694173332525821 - }, - "fcp": { - "mobile": 0.5064211838536264, - "desktop": 0.7747447878222359 - }, - "ttfb": { - "mobile": 0.47751544287661296, - "desktop": 0.6729342084684966 - }, - "inp": { - "mobile": 0.5834672671143503, - "desktop": 0.9403191203369371 - } - } -] \ No newline at end of file diff --git a/packages/cwv-stats/src/cwv/cwv.ts b/packages/cwv-stats/src/cwv/cwv.ts index 06ca1ee8..b8ff03d3 100644 --- a/packages/cwv-stats/src/cwv/cwv.ts +++ b/packages/cwv-stats/src/cwv/cwv.ts @@ -7,6 +7,7 @@ import { // httparchive allows us to pull FID but does not include any metrics at this current time so we can ignore it. type FrameworkCWV = { + id: string, framework: Framework date: string } & { @@ -31,12 +32,9 @@ export async function getLatestFrameworksCWV(): Promise> { } async function getHttpArchiveCWV() { - const url = new URL('https://cdn.httparchive.org/v1/cwv') - frameworks.forEach((framework) => - url.searchParams.append('technology', framework), - ) - url.searchParams.append('geo', 'ALL') - url.searchParams.append('rank', 'ALL') + const baseUrl = 'https://cdn.httparchive.org/v1/cwv' + const queryString = frameworks.map(buildFrameworkQueryParam).join('&') + const url = `${baseUrl}?${queryString}&geo=ALL&rank=ALL` const response = await fetch(url) if (!response.ok) { @@ -48,6 +46,10 @@ async function getHttpArchiveCWV() { return data } +function buildFrameworkQueryParam(framework: Framework) { + return `technology=${framework.replace(" ", "+")}` +} + function getLatestCWVForFrameworks(frameworksCWV: HTTPArchiveCWVSnapshot[]) { const latestStats = new Map() @@ -70,6 +72,7 @@ function validateAllCWVIsSameDate( function buildFrameworkCWV(latestFrameworkCWV: HTTPArchiveCWVSnapshot[]) { const frameworkVitals = latestFrameworkCWV.map((stat) => ({ + id: stat.technology.toLowerCase().replace(".", "-"), framework: stat.technology, date: stat.date, overall: getCWV('overall', stat), @@ -91,13 +94,24 @@ function getCWV(cwv: HTTPArchiveCWV, stat: HTTPArchiveCWVSnapshot) { ) } - const hasMobileVital = vital.mobile.tested > 0 - const hasDesktopVital = vital.desktop.tested > 0 + const vitalMobile = vital.mobile ?? { + tested: 0, + good_number: 0 + } + + const vitalDesktop = vital.desktop ?? { + tested: 0, + good_number: 0 + } + + + const hasMobileVital = vitalMobile.tested > 0 + const hasDesktopVital = vitalDesktop.tested > 0 return { - mobile: hasMobileVital ? vital.mobile.good_number / vital.mobile.tested : 0, + mobile: hasMobileVital ? vitalMobile.good_number / vitalMobile.tested : 0, desktop: hasDesktopVital - ? vital.desktop.good_number / vital.desktop.tested + ? vitalDesktop.good_number / vitalDesktop.tested : 0, } } diff --git a/packages/cwv-stats/src/frameworks/frameworks.ts b/packages/cwv-stats/src/frameworks/frameworks.ts index 31a560aa..795c76e0 100644 --- a/packages/cwv-stats/src/frameworks/frameworks.ts +++ b/packages/cwv-stats/src/frameworks/frameworks.ts @@ -1,3 +1,10 @@ -export const frameworks = ['Svelte', 'Next.js'] as const +export const frameworks = [ + 'Next.js', + 'SolidStart', + 'Astro', + 'Nuxt.js', + 'SvelteKit', + 'React Router' +] as const export type Framework = (typeof frameworks)[number] diff --git a/packages/cwv-stats/src/httparchive/httparchive.ts b/packages/cwv-stats/src/httparchive/httparchive.ts index 1dcd7b9e..fcc8db56 100644 --- a/packages/cwv-stats/src/httparchive/httparchive.ts +++ b/packages/cwv-stats/src/httparchive/httparchive.ts @@ -20,8 +20,8 @@ export type HTTPArchiveCWV = z.infer const httpArchiveVitalSchema = z.object({ name: httpArchiveCWVSchema, - desktop: httpArchiveDeviceSchema, - mobile: httpArchiveDeviceSchema, + desktop: httpArchiveDeviceSchema.nullish(), + mobile: httpArchiveDeviceSchema.nullish(), }) const httpArchiveCWVSnapshot = z.object({ diff --git a/packages/docs/src/components/ChartTabs.astro b/packages/docs/src/components/ChartTabs.astro index b0258cef..5ec5495a 100644 --- a/packages/docs/src/components/ChartTabs.astro +++ b/packages/docs/src/components/ChartTabs.astro @@ -6,19 +6,29 @@ interface Props { tab2Label: string tab3Label?: string tab4Label?: string + tab5Label?: string } -const { sectionId, label, tab1Label, tab2Label, tab3Label, tab4Label } = - Astro.props +const { + sectionId, + label, + tab1Label, + tab2Label, + tab3Label, + tab4Label, + tab5Label, +} = Astro.props const tab1Id = `${sectionId}-tab-1` const tab2Id = `${sectionId}-tab-2` const tab3Id = `${sectionId}-tab-3` const tab4Id = `${sectionId}-tab-4` +const tab5Id = `${sectionId}-tab-5` const panel1Id = `${sectionId}-panel-1` const panel2Id = `${sectionId}-panel-2` const panel3Id = `${sectionId}-panel-3` const panel4Id = `${sectionId}-panel-4` +const panel5Id = `${sectionId}-panel-5` ---
@@ -71,6 +81,20 @@ const panel4Id = `${sectionId}-panel-4` ) } + { + tab5Label && ( + + ) + }
) } + { + tab5Label && ( + + ) + }
diff --git a/packages/docs/src/components/ComparisonBarChart.astro b/packages/docs/src/components/ComparisonBarChart.astro index 58e3f9b8..77cd589e 100644 --- a/packages/docs/src/components/ComparisonBarChart.astro +++ b/packages/docs/src/components/ComparisonBarChart.astro @@ -3,6 +3,7 @@ import type { ChartDatum } from '../lib/types' interface Props { title: string + description?: string data: ChartDatum[] valueFormat: 'count' | 'mb' | 'kb' | 'ms' | 's' yAxisLabel: string @@ -11,7 +12,7 @@ interface Props { const CHART_HEIGHT_PX = 400 const MOBILE_CHART_HEIGHT_PX = 300 -const { title, data, valueFormat, yAxisLabel } = Astro.props +const { title, description, data, valueFormat, yAxisLabel } = Astro.props const chartData = data.map((d) => ({ name: String(d?.name ?? ''), value: Math.max(0, Number(d.value) || 0), @@ -26,6 +27,7 @@ const chartPayload = JSON.stringify({

{title}

+ {description ?

{description}

: null}
{title} chart
diff --git a/packages/docs/src/components/CoreWebVitalChart.astro b/packages/docs/src/components/CoreWebVitalChart.astro new file mode 100644 index 00000000..8340df89 --- /dev/null +++ b/packages/docs/src/components/CoreWebVitalChart.astro @@ -0,0 +1,35 @@ +--- +import { getCWVDesktopStatsChartData } from '../lib/collections' +import type { CWV } from '../lib/collections' +import ComparisonBarChart from './ComparisonBarChart.astro' + +interface Props { + cwv: CWV +} + +const { cwv } = Astro.props + +const cwvTitle: Record = { + lcp: 'Good Largest Contentful Paint', + cls: 'Good Cumulative Layout Shift', + fcp: 'Good First Contentful Paint', + ttfb: 'Good Time To First Byte', + inp: 'Good Interaction to Next Paint', +} + +const cwvDescription: Record = { + lcp: `Measures how fast a page's main content loads. To provide a good user experience, the LCP should be 2.5 seconds or less.`, + cls: `Measures how much and how far content unexpectedly moves on a page. To provide a good user experience, sites should maintain a CLS score of 0.1 or less for at least 75% of page visits.`, + fcp: `Measures initial loading speed. To provide a good user experience, the FCP should be 1.8 seconds or less.`, + ttfb: `Measures the time between the request for a resource and when the first byte of a response begins to arrive. A good TTFB is less than or equal to 800ms.`, + inp: `Measures overall page responsiveness to user actions. To provide a good user experience, the INP should be 200ms or less.`, +} +--- + + diff --git a/packages/docs/src/components/CoreWebVitalsCharts.astro b/packages/docs/src/components/CoreWebVitalsCharts.astro new file mode 100644 index 00000000..d5fb9202 --- /dev/null +++ b/packages/docs/src/components/CoreWebVitalsCharts.astro @@ -0,0 +1,20 @@ +--- +import ChartTabs from './ChartTabs.astro' +import CoreWebVitalChart from './CoreWebVitalChart.astro' +--- + + + + + + + + diff --git a/packages/docs/src/components/CoreWebVitalsTable.astro b/packages/docs/src/components/CoreWebVitalsTable.astro new file mode 100644 index 00000000..ed9abfe3 --- /dev/null +++ b/packages/docs/src/components/CoreWebVitalsTable.astro @@ -0,0 +1,25 @@ +--- +import { cwvStats } from '../lib/collections' +import { getFrameworkSlug } from '../lib/utils' +import StatsTable from './StatsTable.astro' + +const columns = [ + { + key: 'framework', + header: 'Framework', + nameCell: true, + href: (row: Record) =>`/framework/${getFrameworkSlug(row.id as string)}` + }, + { key: 'lcpDesktopPercent', header: 'LCP%' }, + { key: 'clsDesktopPercent', header: 'CLS%' }, + { key: 'fcpDesktopPercent', header: 'FCP%' }, + { key: 'ttfbDesktopPercent', header: 'TTFB%' }, + { key: 'inpDesktopPercent', header: 'INP%' }, +] +--- + + diff --git a/packages/docs/src/content.config.ts b/packages/docs/src/content.config.ts index ee0eb57d..2d66a0d7 100644 --- a/packages/docs/src/content.config.ts +++ b/packages/docs/src/content.config.ts @@ -1,5 +1,5 @@ import { defineCollection } from 'astro:content' -import { glob } from 'astro/loaders' +import { file, glob } from 'astro/loaders' import { z } from 'astro/zod' const timeSchema = z.object({ @@ -111,7 +111,41 @@ const runtimeCollection = defineCollection({ }), }) +const cwvCollection = defineCollection({ + loader: file('src/content/cwv/cwv-stats.json'), + schema: z.object({ + id: z.string(), + framework: z.string(), + date: z.string(), + overall: z.object({ + mobile: z.number(), + desktop: z.number(), + }), + lcp: z.object({ + mobile: z.number(), + desktop: z.number(), + }), + cls: z.object({ + mobile: z.number(), + desktop: z.number(), + }), + fcp: z.object({ + mobile: z.number(), + desktop: z.number(), + }), + ttfb: z.object({ + mobile: z.number(), + desktop: z.number(), + }), + inp: z.object({ + mobile: z.number(), + desktop: z.number(), + }), + }), +}) + export const collections = { devtime: devtimeCollection, runtime: runtimeCollection, + cwv: cwvCollection, } diff --git a/packages/docs/src/content/cwv/cwv-stats.json b/packages/docs/src/content/cwv/cwv-stats.json new file mode 100644 index 00000000..cc190168 --- /dev/null +++ b/packages/docs/src/content/cwv/cwv-stats.json @@ -0,0 +1,176 @@ +[ + { + "id": "sveltekit", + "framework": "SvelteKit", + "date": "2026-05-01", + "overall": { + "mobile": 0.5210084033613446, + "desktop": 0.6167048054919908 + }, + "lcp": { + "mobile": 0.6796462209970136, + "desktop": 0.7981415296640457 + }, + "cls": { + "mobile": 0.8133227417158421, + "desktop": 0.7705923344947735 + }, + "fcp": { + "mobile": 0.604380715817997, + "desktop": 0.7804199694062022 + }, + "ttfb": { + "mobile": 0.5801059446733372, + "desktop": 0.7120402868915001 + }, + "inp": { + "mobile": 0.7564917127071823, + "desktop": 0.9485678113625574 + } + }, + { + "id": "react router", + "framework": "React Router", + "date": "2026-05-01", + "overall": { + "mobile": 0.2791670651238405, + "desktop": 0.4027513140604468 + }, + "lcp": { + "mobile": 0.4100278451879103, + "desktop": 0.6116633151393278 + }, + "cls": { + "mobile": 0.6457572792208933, + "desktop": 0.6211578561991628 + }, + "fcp": { + "mobile": 0.4121720477742092, + "desktop": 0.6662146838895894 + }, + "ttfb": { + "mobile": 0.5382472732085597, + "desktop": 0.7052101211616565 + }, + "inp": { + "mobile": 0.5659162625214427, + "desktop": 0.9045985703739714 + } + }, + { + "id": "nuxt-js", + "framework": "Nuxt.js", + "date": "2026-05-01", + "overall": { + "mobile": 0.28270924275083675, + "desktop": 0.3930229196447214 + }, + "lcp": { + "mobile": 0.45232693803474205, + "desktop": 0.6580843994285622 + }, + "cls": { + "mobile": 0.5810558442805767, + "desktop": 0.5772465970144578 + }, + "fcp": { + "mobile": 0.4374482363184608, + "desktop": 0.6750714274562458 + }, + "ttfb": { + "mobile": 0.454284430514307, + "desktop": 0.5918318969166427 + }, + "inp": { + "mobile": 0.6582373398492547, + "desktop": 0.9590682017583126 + } + }, + { + "id": "next-js", + "framework": "Next.js", + "date": "2026-05-01", + "overall": { + "mobile": 0.329349427286761, + "desktop": 0.5267716499059955 + }, + "lcp": { + "mobile": 0.5199002426702694, + "desktop": 0.729213311974591 + }, + "cls": { + "mobile": 0.6908568037325353, + "desktop": 0.699733991079024 + }, + "fcp": { + "mobile": 0.500675157955559, + "desktop": 0.7669949081447254 + }, + "ttfb": { + "mobile": 0.479252936427325, + "desktop": 0.6726856449569246 + }, + "inp": { + "mobile": 0.5866836931891384, + "desktop": 0.9409772887887863 + } + }, + { + "id": "astro", + "framework": "Astro", + "date": "2026-05-01", + "overall": { + "mobile": 0.6768284574468085, + "desktop": 0.8117316659601526 + }, + "lcp": { + "mobile": 0.7713736154407376, + "desktop": 0.9188860062476836 + }, + "cls": { + "mobile": 0.9267192068132706, + "desktop": 0.873745213701749 + }, + "fcp": { + "mobile": 0.7136175457447145, + "desktop": 0.9160391954615781 + }, + "ttfb": { + "mobile": 0.6091686350177983, + "desktop": 0.8251350582883139 + }, + "inp": { + "mobile": 0.8617109816461518, + "desktop": 0.9752736881842204 + } + }, + { + "id": "solidstart", + "framework": "SolidStart", + "date": "2026-05-01", + "overall": { + "mobile": 0.6363636363636364, + "desktop": 0.6666666666666666 + }, + "lcp": { + "mobile": 0.9090909090909091, + "desktop": 0.9166666666666666 + }, + "cls": { + "mobile": 0.6363636363636364, + "desktop": 0.6666666666666666 + }, + "fcp": { + "mobile": 0.9090909090909091, + "desktop": 0.9166666666666666 + }, + "ttfb": { + "mobile": 0.8181818181818182, + "desktop": 0.8333333333333334 + }, + "inp": { + "mobile": 1, + "desktop": 1 + } + } +] \ No newline at end of file diff --git a/packages/docs/src/lib/collections.ts b/packages/docs/src/lib/collections.ts index 82e344a1..ee7702c3 100644 --- a/packages/docs/src/lib/collections.ts +++ b/packages/docs/src/lib/collections.ts @@ -3,6 +3,38 @@ import { formatBytesToMB, formatTimeMs } from './utils' const devtimeEntries = await getCollection('devtime') export const runtimeEntries = await getCollection('runtime') +const cwvEntries = await getCollection('cwv') + +export const cwvStats = cwvEntries + .map(entry => entry.data) + .sort((a, b) => a.overall.desktop - b.overall.desktop) + .map(stat => ({ + id: stat.id, + framework: stat.framework, + isFocused: true, + lcpDesktopPercent: Math.floor(stat.lcp.desktop * 100), + lcpMobilePercent: Math.floor(stat.lcp.mobile * 100), + clsDesktopPercent: Math.floor(stat.cls.desktop * 100), + clsMobilePercent: Math.floor(stat.cls.mobile * 100), + fcpDesktopPercent: Math.floor(stat.fcp.desktop * 100), + fcpMobilePercent: Math.floor(stat.fcp.mobile * 100), + ttfbDesktopPercent: Math.floor(stat.ttfb.desktop * 100), + ttfbMobilePercent: Math.floor(stat.ttfb.mobile * 100), + inpDesktopPercent: Math.floor(stat.inp.desktop * 100), + inpMobilePercent: Math.floor(stat.inp.mobile * 100) + })) + +export type CWV = 'lcp' | 'cls' | 'fcp' | 'ttfb' | 'inp' + +export function getCWVDesktopStatsChartData(cwv: CWV) { + return cwvStats + .sort((a, b) => a[`${cwv}DesktopPercent`] - b[`${cwv}DesktopPercent`]) + .map(stat => ({ + name: stat.framework, + value: stat[`${cwv}DesktopPercent`], + focused: true + })) +} export const starterStats = devtimeEntries .map((entry) => entry.data) diff --git a/packages/docs/src/pages/run-time.astro b/packages/docs/src/pages/run-time.astro index 683dea29..aeeb68c6 100644 --- a/packages/docs/src/pages/run-time.astro +++ b/packages/docs/src/pages/run-time.astro @@ -14,6 +14,8 @@ import SSRRequestThroughputStatsMethodologyNotes from '../components/SSRRequestT import SSRRequestThroughputCharts from '../components/SSRRequestThroughputCharts.astro' import SSRRequestThroughputStatsTable from '../components/SSRRequestThroughputStatsTable.astro' import Layout from '../layouts/Layout.astro' +import CoreWebVitalsTable from '../components/CoreWebVitalsTable.astro' +import CoreWebVitalsCharts from '../components/CoreWebVitalsCharts.astro' --- @@ -37,4 +39,7 @@ import Layout from '../layouts/Layout.astro' +

Core Web Vitals Desktop

+ +
From 7b063d2c6daf6c18a71fc7af3b243d17320e57bb Mon Sep 17 00:00:00 2001 From: rf Date: Sun, 28 Jun 2026 11:25:29 +1000 Subject: [PATCH 2/5] chore(cwv): update id replace space with dash --- packages/cwv-stats/src/cwv/cwv.ts | 2 +- packages/docs/src/content/cwv/cwv-stats.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cwv-stats/src/cwv/cwv.ts b/packages/cwv-stats/src/cwv/cwv.ts index b8ff03d3..b0d0d1df 100644 --- a/packages/cwv-stats/src/cwv/cwv.ts +++ b/packages/cwv-stats/src/cwv/cwv.ts @@ -72,7 +72,7 @@ function validateAllCWVIsSameDate( function buildFrameworkCWV(latestFrameworkCWV: HTTPArchiveCWVSnapshot[]) { const frameworkVitals = latestFrameworkCWV.map((stat) => ({ - id: stat.technology.toLowerCase().replace(".", "-"), + id: stat.technology.toLowerCase().replace(/[.\s]/g, "-"), framework: stat.technology, date: stat.date, overall: getCWV('overall', stat), diff --git a/packages/docs/src/content/cwv/cwv-stats.json b/packages/docs/src/content/cwv/cwv-stats.json index cc190168..af583e67 100644 --- a/packages/docs/src/content/cwv/cwv-stats.json +++ b/packages/docs/src/content/cwv/cwv-stats.json @@ -29,7 +29,7 @@ } }, { - "id": "react router", + "id": "react-router", "framework": "React Router", "date": "2026-05-01", "overall": { From 266dc3c2a9b93679730e6904dc24861057b217a3 Mon Sep 17 00:00:00 2001 From: rf Date: Sun, 28 Jun 2026 11:34:22 +1000 Subject: [PATCH 3/5] chore(cwv): reverse ordering best first --- packages/docs/src/lib/collections.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/docs/src/lib/collections.ts b/packages/docs/src/lib/collections.ts index ee7702c3..9aaa2162 100644 --- a/packages/docs/src/lib/collections.ts +++ b/packages/docs/src/lib/collections.ts @@ -7,7 +7,7 @@ const cwvEntries = await getCollection('cwv') export const cwvStats = cwvEntries .map(entry => entry.data) - .sort((a, b) => a.overall.desktop - b.overall.desktop) + .sort((a, b) => b.overall.desktop - a.overall.desktop) .map(stat => ({ id: stat.id, framework: stat.framework, @@ -28,7 +28,7 @@ export type CWV = 'lcp' | 'cls' | 'fcp' | 'ttfb' | 'inp' export function getCWVDesktopStatsChartData(cwv: CWV) { return cwvStats - .sort((a, b) => a[`${cwv}DesktopPercent`] - b[`${cwv}DesktopPercent`]) + .sort((a, b) => b[`${cwv}DesktopPercent`] - a[`${cwv}DesktopPercent`]) .map(stat => ({ name: stat.framework, value: stat[`${cwv}DesktopPercent`], From a9a1479e2f146f5748486d6933f70d705a70319a Mon Sep 17 00:00:00 2001 From: rf Date: Sun, 28 Jun 2026 12:19:29 +1000 Subject: [PATCH 4/5] format(cwv): run formatter --- .github/workflows/cwv-stats-monthly.yml | 2 +- packages/cwv-stats/src/cwv/cwv.ts | 11 +++++------ packages/cwv-stats/src/frameworks/frameworks.ts | 12 ++++++------ .../docs/src/components/CoreWebVitalsTable.astro | 9 +++------ packages/docs/src/lib/collections.ts | 10 +++++----- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/.github/workflows/cwv-stats-monthly.yml b/.github/workflows/cwv-stats-monthly.yml index 1168eac8..210a59ac 100644 --- a/.github/workflows/cwv-stats-monthly.yml +++ b/.github/workflows/cwv-stats-monthly.yml @@ -5,7 +5,7 @@ on: # of the following month therefore we run the crawl on the 7th to give a full weeks buffer after the crawl finishes. # https://har.fyi/guides/release-cycle/#running-the-crawl schedule: - - cron: "0 0 7 * *" # At 00:00 on day-of-month 7 (UTC) + - cron: '0 0 7 * *' # At 00:00 on day-of-month 7 (UTC) workflow_dispatch: # Allow manual triggering jobs: diff --git a/packages/cwv-stats/src/cwv/cwv.ts b/packages/cwv-stats/src/cwv/cwv.ts index b0d0d1df..314eaec9 100644 --- a/packages/cwv-stats/src/cwv/cwv.ts +++ b/packages/cwv-stats/src/cwv/cwv.ts @@ -7,7 +7,7 @@ import { // httparchive allows us to pull FID but does not include any metrics at this current time so we can ignore it. type FrameworkCWV = { - id: string, + id: string framework: Framework date: string } & { @@ -47,7 +47,7 @@ async function getHttpArchiveCWV() { } function buildFrameworkQueryParam(framework: Framework) { - return `technology=${framework.replace(" ", "+")}` + return `technology=${framework.replace(' ', '+')}` } function getLatestCWVForFrameworks(frameworksCWV: HTTPArchiveCWVSnapshot[]) { @@ -72,7 +72,7 @@ function validateAllCWVIsSameDate( function buildFrameworkCWV(latestFrameworkCWV: HTTPArchiveCWVSnapshot[]) { const frameworkVitals = latestFrameworkCWV.map((stat) => ({ - id: stat.technology.toLowerCase().replace(/[.\s]/g, "-"), + id: stat.technology.toLowerCase().replace(/[.\s]/g, '-'), framework: stat.technology, date: stat.date, overall: getCWV('overall', stat), @@ -96,15 +96,14 @@ function getCWV(cwv: HTTPArchiveCWV, stat: HTTPArchiveCWVSnapshot) { const vitalMobile = vital.mobile ?? { tested: 0, - good_number: 0 + good_number: 0, } const vitalDesktop = vital.desktop ?? { tested: 0, - good_number: 0 + good_number: 0, } - const hasMobileVital = vitalMobile.tested > 0 const hasDesktopVital = vitalDesktop.tested > 0 diff --git a/packages/cwv-stats/src/frameworks/frameworks.ts b/packages/cwv-stats/src/frameworks/frameworks.ts index 795c76e0..686ea568 100644 --- a/packages/cwv-stats/src/frameworks/frameworks.ts +++ b/packages/cwv-stats/src/frameworks/frameworks.ts @@ -1,10 +1,10 @@ export const frameworks = [ - 'Next.js', - 'SolidStart', - 'Astro', - 'Nuxt.js', - 'SvelteKit', - 'React Router' + 'Next.js', + 'SolidStart', + 'Astro', + 'Nuxt.js', + 'SvelteKit', + 'React Router', ] as const export type Framework = (typeof frameworks)[number] diff --git a/packages/docs/src/components/CoreWebVitalsTable.astro b/packages/docs/src/components/CoreWebVitalsTable.astro index ed9abfe3..967d43e7 100644 --- a/packages/docs/src/components/CoreWebVitalsTable.astro +++ b/packages/docs/src/components/CoreWebVitalsTable.astro @@ -8,7 +8,8 @@ const columns = [ key: 'framework', header: 'Framework', nameCell: true, - href: (row: Record) =>`/framework/${getFrameworkSlug(row.id as string)}` + href: (row: Record) => + `/framework/${getFrameworkSlug(row.id as string)}`, }, { key: 'lcpDesktopPercent', header: 'LCP%' }, { key: 'clsDesktopPercent', header: 'CLS%' }, @@ -18,8 +19,4 @@ const columns = [ ] --- - + diff --git a/packages/docs/src/lib/collections.ts b/packages/docs/src/lib/collections.ts index 9aaa2162..fa493368 100644 --- a/packages/docs/src/lib/collections.ts +++ b/packages/docs/src/lib/collections.ts @@ -6,9 +6,9 @@ export const runtimeEntries = await getCollection('runtime') const cwvEntries = await getCollection('cwv') export const cwvStats = cwvEntries - .map(entry => entry.data) + .map((entry) => entry.data) .sort((a, b) => b.overall.desktop - a.overall.desktop) - .map(stat => ({ + .map((stat) => ({ id: stat.id, framework: stat.framework, isFocused: true, @@ -21,7 +21,7 @@ export const cwvStats = cwvEntries ttfbDesktopPercent: Math.floor(stat.ttfb.desktop * 100), ttfbMobilePercent: Math.floor(stat.ttfb.mobile * 100), inpDesktopPercent: Math.floor(stat.inp.desktop * 100), - inpMobilePercent: Math.floor(stat.inp.mobile * 100) + inpMobilePercent: Math.floor(stat.inp.mobile * 100), })) export type CWV = 'lcp' | 'cls' | 'fcp' | 'ttfb' | 'inp' @@ -29,10 +29,10 @@ export type CWV = 'lcp' | 'cls' | 'fcp' | 'ttfb' | 'inp' export function getCWVDesktopStatsChartData(cwv: CWV) { return cwvStats .sort((a, b) => b[`${cwv}DesktopPercent`] - a[`${cwv}DesktopPercent`]) - .map(stat => ({ + .map((stat) => ({ name: stat.framework, value: stat[`${cwv}DesktopPercent`], - focused: true + focused: true, })) } From 200d21fb5934f63f477ab6d4fed3da009de4718e Mon Sep 17 00:00:00 2001 From: rf Date: Sun, 28 Jun 2026 12:32:54 +1000 Subject: [PATCH 5/5] chore(cwv): update prettier ignore --- .prettierignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 6d692181..0065553a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,4 +11,4 @@ **/output .changeset/*.md pnpm-lock.yaml -/packages/cwv-stats/cwv-stats.json +/packages/docs/src/content/cwv/cwv-stats.json