Methodology
Framework Tracker compares two kinds of projects. Dev Time stats come from starter projects: each framework set up with its default starter-style configuration. Run Time stats come from app packages: the same simple app implemented across frameworks as consistently as each framework allows.
The goal is to show both the default project cost developers inherit when starting a framework and the runtime cost of serving and hydrating a comparable app. Timing results are run multiple times and averaged, and generated JSON is published into the docs package.
Dev Time
Dev Time measurements use the repository's starter-* packages. These projects represent each framework's default setup as closely as possible, so the stats capture the dependency footprint, install cost, build cost, and generated output of a typical new project.
Dependency Counts
- Production and development dependency counts come from each starter package's
package.json. - Direct dependency counts are combined with e18e dependency analysis output when available, including duplicate dependency counts and install size.
- Dependency graph links point to npmgraph using the tracked starter package as the input package.
Node Modules Size
- Install benchmarks copy the starter package to a temporary directory, remove
node_modules, prune the package manager store when possible, and runpnpm install --no-frozen-lockfile. - Full
node_modulessize is measured after the regular install. - Production-only
node_modulessize is measured after removing the full install and runningpnpm install --prod --no-frozen-lockfile.
Build and Install Times
- Install time measures a clean
pnpm install --no-frozen-lockfilein a temporary copy of the starter package. - Install benchmarks run 5 times by default and report average, minimum, and maximum duration.
- Cold build time removes the configured build output directory before running
pnpm build. - Warm build time runs
pnpm buildagain after the cold build, preserving whatever cache or generated output the framework leaves in place. - Build benchmarks run 5 times by default and report average, minimum, and maximum duration.
- Build output size is the total size of the configured production output directory after the final build run.
Core-JS Polyfills
- The scanner searches JavaScript build output files for vendored core-js signatures.
- Detected core-js versions are compared with the modules required by the last 2 major versions of Chrome, Firefox, Safari, and Edge.
- Unnecessary module counts represent polyfill modules already natively supported by that browser target.
- Size is approximate: it reflects the JavaScript chunk containing core-js, which may include other bundled code.
Browser Baseline
- The scanner uses baseline-detector to statically analyze JavaScript from each starter package's browser-facing production build output.
- The scan includes source-like files that baseline-detector supports from the production build output, including JavaScript, TypeScript, Vue, and Svelte files. Known server, cache, trace, type, and build-tool output is excluded.
- Baseline is baseline-detector's overall target result:
high,low, orlimited. When the result is nothigh, baseline-detector also reports the feature ID that determined it. - Feature is the feature ID that determined a non-high Baseline result.
- Year is the Baseline year the project targets: the newest feature it relies on, or blank if any detected feature is not yet Baseline.
- Features is the number of unique web platform feature IDs detected in the browser-facing build output.
Duplicate Dependencies
- Duplicate dependency details come from e18e dependency analysis messages collected for each starter package.
- A duplicate dependency means multiple installed versions of the same package were found in the starter's dependency tree.
- Framework detail pages show the package name, installed versions, and the dependency paths reported by the analyzer.
Run Time
Run Time measurements use the repository's app-* packages. These apps implement the same small benchmark routes and data shape wherever possible, so the stats focus on browser rendering, server rendering, request-handler throughput, and load behavior for comparable production apps.
Client Side Rendered Tests
- Each framework renders a table of 1000 rows with two UUID columns.
- Metrics are measured with Lighthouse flow in Chromium through Puppeteer.
- First Paint and First Contentful Paint are measured on initial navigation to
/client-side-rendered. - Interaction to Next Paint is measured by clicking the first row's detail link and waiting for the detail view.
- Benchmarks run 5 times by default and average successful metrics.
- Next.js wraps the client-side rendered table in a
dynamicimport withssr: falseto prevent build-time prerendering. - TanStack Start, Nuxt, SvelteKit, and SolidStart disable SSR per route.
- React Router uses route-level
clientLoaderfunctions withHydrateFallbackso the client-rendered routes are not server-rendered. - Astro uses client-only React islands for client-side rendered routes.
- Client-side rendered tests use each framework's normal production build because SPA-only build modes are not supported consistently across the compared frameworks.
- Astro's benchmark table and detail components are React islands rendered with
client:only="react". Astro'sClientRouteris not used for this test because it changes navigation behavior rather than making components client-only.
Server Side Rendered Tests
- Each framework renders a table of 1000 rows with two UUID columns.
- Metrics are measured with Lighthouse flow in Chromium through Puppeteer.
- First Paint and First Contentful Paint are measured on initial navigation to
/server-side-rendered. - Interaction to Next Paint is measured by clicking the first row's detail link and waiting for
/server-side-rendered/:id. - Benchmarks run 5 times by default and average successful metrics.
Server Side Throughput Tests
- Each framework renders the dedicated
/ssr-throughputroute with a table of 1000 rows and UUID id/name columns. - This route intentionally omits detail links and framework link components so router, prefetch, and navigation metadata do not dominate the request handler throughput measurement.
- Mock HTTP requests bypass TCP overhead, so this measures request-handler rendering throughput rather than full network server throughput.
- Data is loaded asynchronously to simulate real-world data fetching.
- Duplication factor indicates how many times each UUID appears in the response. A 1x result is optimal; a 2x result usually means the response includes a hydration payload.
- Benchmarks run for 10 seconds using tinybench.
- Frameworks are invoked through their production request handlers where possible. Web API handlers receive
Requestobjects; Node.js handlers receive mockIncomingMessageandServerResponseobjects. - Next.js renders the throughput table as a client component, matching the setup from PR #94, so the benchmark compares traditional server-rendered React plus hydration work instead of forcing every table row through React Server Components.
- The test is inspired by eknkc/ssr-benchmark.
Server Side Load Test
- Each framework serves the server-rendered table route over a real local HTTP server.
- The measured route is
/server-side-rendered, using the same 1000-row UUID table as the SSR request throughput and browser rendering tests. - Load is applied with autocannon in staged connection counts: 1, 5, 10, 25, 50, 100, and 200 concurrent connections.
- Each stage runs for approximately 5 seconds.
- Peak requests/sec is the highest successful stage throughput observed during the staged run.
- P90 and P99 latency are compared at the 25-, 50-, and 100-connection stages for every framework, so latency is measured under the same concurrency pressure.
- Total requests cover the full staged load run, not only the peak stage.
Core Web Vitals
- Core Web Vital metrics are sourced from HTTP Archive technology reports.
- The docs publish framework-level desktop and mobile percentages from the latest collected HTTP Archive snapshot in the repository.
- Metrics refresh monthly when new HTTP Archive data is collected.