From 78b679a0ad9bf868f3759424a19cbf0338067d26 Mon Sep 17 00:00:00 2001 From: Alexander Karan Date: Sun, 22 Feb 2026 20:45:58 +0800 Subject: [PATCH 1/2] Docs Clean Up Part One --- .../docs/src/components/DependencyStats.astro | 514 +++--------------- .../docs/src/components/Description.astro | 23 + .../docs/src/components/DetailsLink.astro | 32 ++ .../docs/src/components/FrameworkDetail.astro | 190 ++----- .../src/components/MethodologyNotes.astro | 68 +++ .../docs/src/components/MethodologyTag.astro | 15 + .../docs/src/components/SSRStatsTable.astro | 41 ++ packages/docs/src/components/StatsTable.astro | 160 ++++++ packages/docs/src/lib/collections.ts | 9 + packages/docs/src/pages/index.astro | 6 + packages/docs/src/pages/run-time.astro | 45 ++ packages/docs/src/styles/shared.css | 24 + 12 files changed, 558 insertions(+), 569 deletions(-) create mode 100644 packages/docs/src/components/Description.astro create mode 100644 packages/docs/src/components/DetailsLink.astro create mode 100644 packages/docs/src/components/MethodologyNotes.astro create mode 100644 packages/docs/src/components/MethodologyTag.astro create mode 100644 packages/docs/src/components/SSRStatsTable.astro create mode 100644 packages/docs/src/components/StatsTable.astro create mode 100644 packages/docs/src/lib/collections.ts diff --git a/packages/docs/src/components/DependencyStats.astro b/packages/docs/src/components/DependencyStats.astro index 0ba299c..03daba0 100644 --- a/packages/docs/src/components/DependencyStats.astro +++ b/packages/docs/src/components/DependencyStats.astro @@ -10,16 +10,72 @@ import '../styles/shared.css' import ComparisonBarChart from './ComparisonBarChart.astro' import FocusedToggle from './FocusedToggle.astro' import PageHeader from './PageHeader.astro' +import Description from './Description.astro' +import MethodologyTag from './MethodologyTag.astro' +import StatsTable from './StatsTable.astro' const devtimeEntries = await getCollection('devtime') -const runtimeEntries = await getCollection('runtime') const starterStats = devtimeEntries .map((entry) => entry.data) .sort((a, b) => a.order - b.order) -const ssrStats = runtimeEntries - .map((entry) => entry.data) - .sort((a, b) => a.order - b.order) + +const depsColumns = [ + { + key: 'name', + header: 'Framework', + href: (row: Record) => + `/framework/${getFrameworkSlug(row.package as string)}`, + }, + { key: 'prodDependencies', header: 'Prod Deps' }, + { key: 'devDependencies', header: 'Dev Deps' }, + { key: 'nodeModulesSize', header: 'Size' }, + { key: 'nodeModulesSizeProdOnly', header: 'Size (Prod Only)' }, + { + key: 'graph', + header: 'Graph', + href: (row: Record) => + `https://npmgraph.js.org/?q=https://github.com/e18e/framework-tracker/blob/main/packages/${row.package}/package.json`, + }, +] + +const depsData = starterStats.map((f) => ({ + name: f.name, + package: f.package, + isFocused: f.isFocused, + prodDependencies: f.prodDependencies, + devDependencies: f.devDependencies, + nodeModulesSize: formatBytesToMB(f.nodeModulesSize), + nodeModulesSizeProdOnly: formatBytesToMB(f.nodeModulesSizeProdOnly), + graph: 'View', +})) + +const buildColumns = [ + { + key: 'name', + header: 'Framework', + href: (row: Record) => + `/framework/${getFrameworkSlug(row.package as string)}`, + }, + { key: 'avgInstall', header: 'Avg Install' }, + { key: 'minInstall', header: 'Min Install' }, + { key: 'maxInstall', header: 'Max Install' }, + { key: 'avgColdBuild', header: 'Avg Cold Build' }, + { key: 'avgWarmBuild', header: 'Avg Warm Build' }, + { key: 'buildOutput', header: 'Build Output' }, +] + +const buildData = starterStats.map((f) => ({ + name: f.name, + package: f.package, + isFocused: f.isFocused, + avgInstall: formatTimeMs(f.installTime.avgMs), + minInstall: formatTimeMs(f.installTime.minMs), + maxInstall: formatTimeMs(f.installTime.maxMs), + avgColdBuild: formatTimeMs(f.coldBuildTime.avgMs), + avgWarmBuild: formatTimeMs(f.warmBuildTime.avgMs), + buildOutput: formatBytesToMB(f.buildOutputSize), +})) const validForCharts = starterStats.filter( (f) => @@ -30,26 +86,26 @@ const validForCharts = starterStats.filter( Number.isFinite(f.nodeModulesSizeProdOnly), ) -const depsData = validForCharts.map((f) => ({ +const chartDepsData = validForCharts.map((f) => ({ name: f.name, value: f.devDependencies, })) -const prodDepsData = validForCharts.map((f) => ({ +const chartProdDepsData = validForCharts.map((f) => ({ name: f.name, value: f.prodDependencies, })) -const buildSizeData = validForCharts.map((f) => ({ +const chartBuildSizeData = validForCharts.map((f) => ({ name: f.name, value: f.buildOutputSize / BYTES_PER_MB, })) -const buildSizeProdData = validForCharts.map((f) => ({ +const chartBuildSizeProdData = validForCharts.map((f) => ({ name: f.name, value: f.nodeModulesSizeProdOnly / BYTES_PER_MB, })) --- Framework Tracker -

+ For a full list of what we plan to track see{' '} ({ > roadmap . -

+ -

+ Stats are currently in active development and may change as we refine our methodology and testing process. -

+

Dev Time Performance

-

+ Measured using pnpm on GitHub Actions (ubuntu-latest, Node 24) based on the starter project set up by each framework's CLI. -

- -
- - - - - - - - - - - - - { - starterStats.map((framework) => ( - - - - - - - - - )) - } - -
FrameworkDev DepsSizeSize (Prod Only)Graph
- - {framework.name} - - {framework.prodDependencies}{framework.devDependencies}{formatBytesToMB(framework.nodeModulesSize)}{formatBytesToMB(framework.nodeModulesSizeProdOnly)} - - View - -
-
+ + +
({ aria-labelledby="deps-deps-tab" class="tabpanel chart-tabpanel active" > - +
({ >
-
- - - - - - - - - - - - - - { - starterStats.map((framework) => ( - - - - - - - - - - )) - } - -
FrameworkAvg InstallMin InstallMax InstallAvg Cold BuildAvg Warm BuildBuild Output
- - {framework.name} - - {formatTimeMs(framework.installTime.avgMs)}{formatTimeMs(framework.installTime.minMs)}{formatTimeMs(framework.installTime.maxMs)}{formatTimeMs(framework.coldBuildTime.avgMs)}{formatTimeMs(framework.warmBuildTime.avgMs)}{formatBytesToMB(framework.buildOutputSize)}
-
+
({ >
@@ -259,244 +248,14 @@ const buildSizeProdData = validForCharts.map((f) => ({ >
-

Runtime Performance

- -

SSR Performance

- -

- Measured on GitHub Actions (ubuntu-latest, Node 24) using custom SSR benchmark - apps. -

- -
- - - - - - - - - - - - { - ssrStats.map((framework) => ( - - - - - - - - )) - } - -
FrameworkOps/secAvg LatencyBody SizeDuplication
- {framework.package === 'app-baseline-html' ? ( - {framework.name} - ) : ( - - {framework.name} - - )} - {framework.ssrOpsPerSec?.toLocaleString()}{framework.ssrAvgLatencyMs}ms{framework.ssrBodySizeKb}kb{framework.ssrDuplicationFactor}x
-
- -
-

Methodology

-
    -
  • Each framework renders a table of 1000 rows with two UUID columns
  • -
  • - Mock HTTP requests bypass TCP overhead for accurate rendering measurement -
  • -
  • Data is loaded asynchronously to simulate real-world data fetching
  • -
  • - Duplication factor indicates how many times each UUID appears in the - response (1x = optimal, 2x = includes hydration payload) -
  • -
  • - Benchmarks run for 10 seconds using tinybench -
  • -
  • - Astro, Nuxt, and SvelteKit handle Node.js HTTP requests natively. React - Router, SolidStart, and TanStack Start use Web APIs internally, so - benchmarks include the cost of their Node.js adapter layers (@react-router/node, h3, and srvx respectively) -
  • -
  • - Next.js defaults to React Server Components (RSC), a different rendering - model than traditional SSR. To keep the comparison fair, Next.js uses "use client" to opt out of RSC and use traditional SSR + hydration like most of the other - frameworks -
  • -
  • - Inspired by eknkc/ssr-benchmark -
  • -
-
- diff --git a/packages/docs/src/components/DetailsLink.astro b/packages/docs/src/components/DetailsLink.astro new file mode 100644 index 0000000..0d00847 --- /dev/null +++ b/packages/docs/src/components/DetailsLink.astro @@ -0,0 +1,32 @@ +--- +interface Props { + href: string + label: string +} + +const { href, label } = Astro.props +--- + + + + diff --git a/packages/docs/src/components/FrameworkDetail.astro b/packages/docs/src/components/FrameworkDetail.astro index 87b51e8..eabaf0a 100644 --- a/packages/docs/src/components/FrameworkDetail.astro +++ b/packages/docs/src/components/FrameworkDetail.astro @@ -2,6 +2,8 @@ import type { CollectionEntry } from 'astro:content' import { formatBytesToMB, formatTimeMs } from '../lib/utils' import DevTimeChart from './DevTimeChart.astro' +import MethodologyTag from './MethodologyTag.astro' +import MethodologyNotes from './MethodologyNotes.astro' import '../styles/shared.css' import PageHeader from './PageHeader.astro' @@ -43,10 +45,10 @@ const measuredDateDisplay = (() => {

Dev Time Performance

-

+ Measured using pnpm on GitHub Actions (ubuntu-latest, Node 24) based on the starter project set up by each framework's CLI. -

+
@@ -130,10 +132,10 @@ const measuredDateDisplay = (() => {

Runtime Performance

SSR Performance

-

+ Measured on GitHub Actions (ubuntu-latest, Node 24) using custom SSR benchmark apps. -

+
@@ -166,49 +168,46 @@ const measuredDateDisplay = (() => {
-
-

Methodology

-
    -
  • - Each framework renders a table of 1000 rows with two UUID columns -
  • -
  • - Mock HTTP requests bypass TCP overhead for accurate rendering - measurement -
  • -
  • - Data is loaded asynchronously to simulate real-world data fetching -
  • -
  • - Duplication factor indicates how many times each UUID appears in - the response (1x = optimal, 2x = includes hydration payload) -
  • -
  • - Benchmarks run for 10 seconds using{' '} - - tinybench - -
  • -
  • - React Router uses Web APIs internally; benchmarks include the cost - of its official Node.js adapter (@react-router/node) -
  • -
  • - Inspired by{' '} - - eknkc/ssr-benchmark - -
  • -
-
+ +
  • + Each framework renders a table of 1000 rows with two UUID columns +
  • +
  • + Mock HTTP requests bypass TCP overhead for accurate rendering + measurement +
  • +
  • + Data is loaded asynchronously to simulate real-world data fetching +
  • +
  • + Duplication factor indicates how many times each UUID appears in the + response (1x = optimal, 2x = includes hydration payload) +
  • +
  • + Benchmarks run for 10 seconds using{' '} + + tinybench + +
  • +
  • + React Router uses Web APIs internally; benchmarks include the cost + of its official Node.js adapter (@react-router/node) +
  • +
  • + Inspired by{' '} + + eknkc/ssr-benchmark + +
  • +
    ) } @@ -287,12 +286,6 @@ const measuredDateDisplay = (() => { color: var(--ft-text); } - .methodology { - font-size: 14px; - color: var(--ft-muted); - margin-bottom: 0.5em; - } - .table-wrapper { width: 100%; margin-top: 16px; @@ -309,45 +302,6 @@ const measuredDateDisplay = (() => { color: var(--ft-text); } - .methodology-notes { - margin-top: 2em; - padding: 1em 1.25em; - background: var(--ft-bg-muted); - border-radius: 8px; - border: 1px solid var(--ft-border); - } - - .methodology-notes h4 { - margin: 0 0 0.75em 0; - font-size: 14px; - font-weight: 600; - color: var(--ft-text); - } - - .methodology-notes ul { - margin: 0; - padding-left: 1.25em; - font-size: 13px; - color: var(--ft-muted); - } - - .methodology-notes li { - margin-bottom: 0.4em; - } - - .methodology-notes li:last-child { - margin-bottom: 0; - } - - .methodology-notes a { - color: var(--ft-accent); - text-decoration: none; - } - - .methodology-notes a:hover { - text-decoration: underline; - } - table { width: 100%; border-collapse: collapse; @@ -410,62 +364,10 @@ const measuredDateDisplay = (() => { } /* Dark mode */ - :global(html.dark) .detail { - color: var(--ft-text); - } - - :global(html.dark) .detail-meta { - color: var(--ft-muted); - } - - :global(html.dark) .section h2, - :global(html.dark) .section h3 { - color: var(--ft-text); - } - - :global(html.dark) .methodology { - color: var(--ft-muted); - } - - :global(html.dark) table { - background: var(--ft-bg); - border-color: var(--ft-border); - } - - :global(html.dark) td { - border-bottom-color: var(--ft-border); - color: var(--ft-muted); - } - - :global(html.dark) td.metric-name { - color: var(--ft-text); - } - :global(html.dark) tbody tr:hover { background: rgba(124, 181, 96, 0.1); } - :global(html.dark) .methodology-notes { - background: var(--ft-bg-muted); - border-color: var(--ft-border); - } - - :global(html.dark) .methodology-notes h4 { - color: var(--ft-text); - } - - :global(html.dark) .methodology-notes ul { - color: var(--ft-muted); - } - - :global(html.dark) .build-output { - color: var(--ft-muted); - } - - :global(html.dark) .build-output strong { - color: var(--ft-text); - } - @media screen and (max-width: 768px) { .detail-title { font-size: 26px; diff --git a/packages/docs/src/components/MethodologyNotes.astro b/packages/docs/src/components/MethodologyNotes.astro new file mode 100644 index 0000000..c1dc200 --- /dev/null +++ b/packages/docs/src/components/MethodologyNotes.astro @@ -0,0 +1,68 @@ +--- + +--- + +
    +

    Methodology

    +
      + +
    +
    + + diff --git a/packages/docs/src/components/MethodologyTag.astro b/packages/docs/src/components/MethodologyTag.astro new file mode 100644 index 0000000..6ce4c47 --- /dev/null +++ b/packages/docs/src/components/MethodologyTag.astro @@ -0,0 +1,15 @@ +--- + +--- + +

    + +

    + + diff --git a/packages/docs/src/components/SSRStatsTable.astro b/packages/docs/src/components/SSRStatsTable.astro new file mode 100644 index 0000000..b4b7589 --- /dev/null +++ b/packages/docs/src/components/SSRStatsTable.astro @@ -0,0 +1,41 @@ +--- +import { ssrStats } from '../lib/collections' +import { getFrameworkSlug } from '../lib/utils' +import MethodologyTag from './MethodologyTag.astro' +import StatsTable from './StatsTable.astro' + +const columns = [ + { + key: 'name', + header: 'Framework', + href: (row: Record) => + row.package !== 'app-baseline-html' + ? `/framework/${getFrameworkSlug(row.package as string)}` + : null, + }, + { key: 'ssrOpsPerSec', header: 'Ops/sec' }, + { key: 'ssrAvgLatencyMs', header: 'Avg Latency' }, + { key: 'ssrBodySizeKb', header: 'Body Size' }, + { key: 'ssrDuplicationFactor', header: 'Duplication' }, +] + +const tableData = ssrStats.map((f) => ({ + name: f.name, + package: f.package, + isFocused: f.isFocused, + ssrOpsPerSec: f.ssrOpsPerSec?.toLocaleString() ?? '', + ssrAvgLatencyMs: `${f.ssrAvgLatencyMs}ms`, + ssrBodySizeKb: `${f.ssrBodySizeKb}kb`, + ssrDuplicationFactor: `${f.ssrDuplicationFactor}x`, +})) +--- + + + Measured on GitHub Actions (ubuntu-latest, Node 24) using custom SSR benchmark + apps. + + diff --git a/packages/docs/src/components/StatsTable.astro b/packages/docs/src/components/StatsTable.astro new file mode 100644 index 0000000..2fbbfe0 --- /dev/null +++ b/packages/docs/src/components/StatsTable.astro @@ -0,0 +1,160 @@ +--- +type Row = Record + +interface Column { + key: string + header: string + href?: (row: Row) => string | null | undefined +} + +interface Props { + label: string + columns: Column[] + data: Row[] +} + +const { label, columns, data } = Astro.props +--- + +
    + + + + {columns.map((col) => )} + + + + { + data.map((row) => ( + + {columns.map((col, i) => { + const href = col.href?.(row) + return ( + + ) + })} + + )) + } + +
    {col.header}
    + {href ? ( + + {String(row[col.key] ?? '')} + + ) : ( + String(row[col.key] ?? '') + )} +
    +
    + + diff --git a/packages/docs/src/lib/collections.ts b/packages/docs/src/lib/collections.ts new file mode 100644 index 0000000..4694646 --- /dev/null +++ b/packages/docs/src/lib/collections.ts @@ -0,0 +1,9 @@ +import { getCollection } from 'astro:content' + +const runtimeEntries = await getCollection('runtime') + +const ssrStats = runtimeEntries + .map((entry) => entry.data) + .sort((a, b) => a.order - b.order) + +export { ssrStats } diff --git a/packages/docs/src/pages/index.astro b/packages/docs/src/pages/index.astro index 68fb4f9..2b59847 100644 --- a/packages/docs/src/pages/index.astro +++ b/packages/docs/src/pages/index.astro @@ -1,8 +1,14 @@ --- import DependencyStats from '../components/DependencyStats.astro' +import DetailsLink from '../components/DetailsLink.astro' +import SSRStatsTable from '../components/SSRStatsTable.astro' import Layout from '../layouts/Layout.astro' --- +

    Runtime Performance

    +

    SSR Performance

    + +
    diff --git a/packages/docs/src/pages/run-time.astro b/packages/docs/src/pages/run-time.astro index 95dd510..70ed29c 100644 --- a/packages/docs/src/pages/run-time.astro +++ b/packages/docs/src/pages/run-time.astro @@ -1,8 +1,53 @@ --- +import FocusedToggle from '../components/FocusedToggle.astro' +import MethodologyNotes from '../components/MethodologyNotes.astro' import PageHeader from '../components/PageHeader.astro' +import SSRStatsTable from '../components/SSRStatsTable.astro' import Layout from '../layouts/Layout.astro' --- Run Time Stats + +

    SSR Performance

    + + +
  • Each framework renders a table of 1000 rows with two UUID columns
  • +
  • + Mock HTTP requests bypass TCP overhead for accurate rendering measurement +
  • +
  • Data is loaded asynchronously to simulate real-world data fetching
  • +
  • + Duplication factor indicates how many times each UUID appears in the + response (1x = optimal, 2x = includes hydration payload) +
  • +
  • + Benchmarks run for 10 seconds using tinybench +
  • +
  • + Astro, Nuxt, and SvelteKit handle Node.js HTTP requests natively. React + Router, SolidStart, and TanStack Start use Web APIs internally, so + benchmarks include the cost of their Node.js adapter layers (@react-router/node, h3, and srvx respectively) +
  • +
  • + Next.js defaults to React Server Components (RSC), a different rendering + model than traditional SSR. To keep the comparison fair, Next.js uses "use client" to opt out of RSC and use traditional SSR + hydration like most of the other + frameworks +
  • +
  • + Inspired by eknkc/ssr-benchmark +
  • +
    diff --git a/packages/docs/src/styles/shared.css b/packages/docs/src/styles/shared.css index 9ba6399..1a8ae2c 100644 --- a/packages/docs/src/styles/shared.css +++ b/packages/docs/src/styles/shared.css @@ -23,3 +23,27 @@ html.dark { --ft-bg-muted: #242424; --ft-chart-grid: rgba(255, 255, 255, 0.22); } + +/* ── Headings + labels ─────────────────────────────────────── */ + +h2 { + font-size: 20px; + margin-top: 2em; + margin-bottom: 1em; + font-weight: 600; + color: var(--ft-text); +} + +h3 { + font-size: 16px; + margin-top: 1.5em; + margin-bottom: 0.75em; + font-weight: 600; + color: var(--ft-text); +} + +@media screen and (max-width: 768px) { + h2 { + font-size: 18px; + } +} From baef224c5c2c4e2d2a4272d3082694c9697c31c2 Mon Sep 17 00:00:00 2001 From: Alexander Karan Date: Sun, 22 Feb 2026 20:56:24 +0800 Subject: [PATCH 2/2] Shared notes --- .../docs/src/components/FrameworkDetail.astro | 43 +------------------ .../components/SSRStatsMethodologyNotes.astro | 43 +++++++++++++++++++ packages/docs/src/pages/run-time.astro | 42 +----------------- 3 files changed, 47 insertions(+), 81 deletions(-) create mode 100644 packages/docs/src/components/SSRStatsMethodologyNotes.astro diff --git a/packages/docs/src/components/FrameworkDetail.astro b/packages/docs/src/components/FrameworkDetail.astro index eabaf0a..377c14e 100644 --- a/packages/docs/src/components/FrameworkDetail.astro +++ b/packages/docs/src/components/FrameworkDetail.astro @@ -6,6 +6,7 @@ import MethodologyTag from './MethodologyTag.astro' import MethodologyNotes from './MethodologyNotes.astro' import '../styles/shared.css' import PageHeader from './PageHeader.astro' +import SSRStatsMethodologyNotes from './SSRStatsMethodologyNotes.astro' interface Props { devtime: CollectionEntry<'devtime'>['data'] @@ -167,47 +168,7 @@ const measuredDateDisplay = (() => { - - -
  • - Each framework renders a table of 1000 rows with two UUID columns -
  • -
  • - Mock HTTP requests bypass TCP overhead for accurate rendering - measurement -
  • -
  • - Data is loaded asynchronously to simulate real-world data fetching -
  • -
  • - Duplication factor indicates how many times each UUID appears in the - response (1x = optimal, 2x = includes hydration payload) -
  • -
  • - Benchmarks run for 10 seconds using{' '} - - tinybench - -
  • -
  • - React Router uses Web APIs internally; benchmarks include the cost - of its official Node.js adapter (@react-router/node) -
  • -
  • - Inspired by{' '} - - eknkc/ssr-benchmark - -
  • -
    + ) } diff --git a/packages/docs/src/components/SSRStatsMethodologyNotes.astro b/packages/docs/src/components/SSRStatsMethodologyNotes.astro new file mode 100644 index 0000000..74392e3 --- /dev/null +++ b/packages/docs/src/components/SSRStatsMethodologyNotes.astro @@ -0,0 +1,43 @@ +--- +import MethodologyNotes from '../components/MethodologyNotes.astro' +--- + + +
  • Each framework renders a table of 1000 rows with two UUID columns
  • +
  • + Mock HTTP requests bypass TCP overhead for accurate rendering measurement +
  • +
  • Data is loaded asynchronously to simulate real-world data fetching
  • +
  • + Duplication factor indicates how many times each UUID appears in the + response (1x = optimal, 2x = includes hydration payload) +
  • +
  • + Benchmarks run for 10 seconds using tinybench +
  • +
  • + Astro, Nuxt, and SvelteKit handle Node.js HTTP requests natively. React + Router, SolidStart, and TanStack Start use Web APIs internally, so + benchmarks include the cost of their Node.js adapter layers (@react-router/node, h3, and srvx respectively) +
  • +
  • + Next.js defaults to React Server Components (RSC), a different rendering + model than traditional SSR. To keep the comparison fair, Next.js uses "use client" to opt out of RSC and use traditional SSR + hydration like most of the other + frameworks +
  • +
  • + Inspired by eknkc/ssr-benchmark +
  • +
    diff --git a/packages/docs/src/pages/run-time.astro b/packages/docs/src/pages/run-time.astro index 70ed29c..4b5bdce 100644 --- a/packages/docs/src/pages/run-time.astro +++ b/packages/docs/src/pages/run-time.astro @@ -1,7 +1,7 @@ --- import FocusedToggle from '../components/FocusedToggle.astro' -import MethodologyNotes from '../components/MethodologyNotes.astro' import PageHeader from '../components/PageHeader.astro' +import SSRStatsMethodologyNotes from '../components/SSRStatsMethodologyNotes.astro' import SSRStatsTable from '../components/SSRStatsTable.astro' import Layout from '../layouts/Layout.astro' --- @@ -11,43 +11,5 @@ import Layout from '../layouts/Layout.astro'

    SSR Performance

    - -
  • Each framework renders a table of 1000 rows with two UUID columns
  • -
  • - Mock HTTP requests bypass TCP overhead for accurate rendering measurement -
  • -
  • Data is loaded asynchronously to simulate real-world data fetching
  • -
  • - Duplication factor indicates how many times each UUID appears in the - response (1x = optimal, 2x = includes hydration payload) -
  • -
  • - Benchmarks run for 10 seconds using tinybench -
  • -
  • - Astro, Nuxt, and SvelteKit handle Node.js HTTP requests natively. React - Router, SolidStart, and TanStack Start use Web APIs internally, so - benchmarks include the cost of their Node.js adapter layers (@react-router/node, h3, and srvx respectively) -
  • -
  • - Next.js defaults to React Server Components (RSC), a different rendering - model than traditional SSR. To keep the comparison fair, Next.js uses "use client" to opt out of RSC and use traditional SSR + hydration like most of the other - frameworks -
  • -
  • - Inspired by eknkc/ssr-benchmark -
  • -
    +