From 10caf4997b28ff0f9bfaa9941247b97d29058bef Mon Sep 17 00:00:00 2001 From: Alexander Karan <47707063+AlexanderKaran@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:47:08 +0800 Subject: [PATCH 1/3] Change CSR Set Up --- .../src/routes/client-side-rendered/+page.svelte | 9 ++------- .../src/routes/client-side-rendered/+page.ts | 15 +++++++++++++++ .../routes/client-side-rendered/[id]/+page.svelte | 6 ++---- .../src/routes/client-side-rendered/[id]/+page.ts | 6 ++++++ 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/app-sveltekit/src/routes/client-side-rendered/+page.svelte b/packages/app-sveltekit/src/routes/client-side-rendered/+page.svelte index 4e90d5ff..a1a566c7 100644 --- a/packages/app-sveltekit/src/routes/client-side-rendered/+page.svelte +++ b/packages/app-sveltekit/src/routes/client-side-rendered/+page.svelte @@ -1,15 +1,10 @@ - {#each entries as entry (entry.id)} + {#each data.entries as entry (entry.id)} diff --git a/packages/app-sveltekit/src/routes/client-side-rendered/+page.ts b/packages/app-sveltekit/src/routes/client-side-rendered/+page.ts index 62ad4e4f..c66b16bf 100644 --- a/packages/app-sveltekit/src/routes/client-side-rendered/+page.ts +++ b/packages/app-sveltekit/src/routes/client-side-rendered/+page.ts @@ -1 +1,16 @@ +import type { PageLoad } from './$types' + +type Entry = { id: string; name: string } + +function generateData(): Entry[] { + return Array.from({ length: 1000 }, () => ({ + id: crypto.randomUUID(), + name: crypto.randomUUID(), + })) +} + export const ssr = false + +export const load: PageLoad = () => { + return { entries: generateData() } +} diff --git a/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.svelte b/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.svelte index 39bab2d4..f446fc16 100644 --- a/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.svelte +++ b/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.svelte @@ -1,7 +1,5 @@ -

{id}

+

{data.id}

diff --git a/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.ts b/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.ts index 62ad4e4f..5f70dd65 100644 --- a/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.ts +++ b/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.ts @@ -1 +1,7 @@ +import type { PageLoad } from './$types' + export const ssr = false + +export const load: PageLoad = ({ params }) => { + return { id: params.id } +} From 389bbee2e54aa5a8cefb7fd360f8d5b6bf662de4 Mon Sep 17 00:00:00 2001 From: Alexander Karan <47707063+AlexanderKaran@users.noreply.github.com> Date: Thu, 23 Jul 2026 19:20:13 +0800 Subject: [PATCH 2/3] Fix --- .../src/routes/client-side-rendered/[id]/+page.svelte | 6 ++++-- .../src/routes/client-side-rendered/[id]/+page.ts | 6 ------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.svelte b/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.svelte index f446fc16..d283d2ba 100644 --- a/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.svelte +++ b/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.svelte @@ -1,5 +1,7 @@ -

{data.id}

+

{params.id}

diff --git a/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.ts b/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.ts index 5f70dd65..62ad4e4f 100644 --- a/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.ts +++ b/packages/app-sveltekit/src/routes/client-side-rendered/[id]/+page.ts @@ -1,7 +1 @@ -import type { PageLoad } from './$types' - export const ssr = false - -export const load: PageLoad = ({ params }) => { - return { id: params.id } -} From 1311437cc6d6fec70f4437cf894dcfbd4ede301b Mon Sep 17 00:00:00 2001 From: Alexander Karan <47707063+AlexanderKaran@users.noreply.github.com> Date: Thu, 23 Jul 2026 19:45:59 +0800 Subject: [PATCH 3/3] Added App Shell --- .../src/routes/client-side-rendered/+page.ts | 1 + packages/docs/src/content/docs/methodology.md | 30 ++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/app-sveltekit/src/routes/client-side-rendered/+page.ts b/packages/app-sveltekit/src/routes/client-side-rendered/+page.ts index c66b16bf..e73c4fe5 100644 --- a/packages/app-sveltekit/src/routes/client-side-rendered/+page.ts +++ b/packages/app-sveltekit/src/routes/client-side-rendered/+page.ts @@ -10,6 +10,7 @@ function generateData(): Entry[] { } export const ssr = false +export const prerender = true export const load: PageLoad = () => { return { entries: generateData() } diff --git a/packages/docs/src/content/docs/methodology.md b/packages/docs/src/content/docs/methodology.md index 36724604..7fc536f8 100644 --- a/packages/docs/src/content/docs/methodology.md +++ b/packages/docs/src/content/docs/methodology.md @@ -138,19 +138,35 @@ throughput, and load behavior for comparable production apps. - 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. -- Client-side rendered tests use each framework's normal production build - because SPA-only build modes are not supported consistently across the - compared frameworks. +- These are route-based client-side rendering tests, not full-app SPA mode + tests. Each app uses its normal production build and configures the benchmark + routes so the measured table and detail content are rendered in the browser. +- Full-app SPA modes are not supported consistently across the compared + frameworks. Many of the frameworks are designed around hybrid or + server-capable production builds, so forcing an SPA-specific build would also + introduce different deployment models and framework-specific configuration. +- A framework may statically generate or prerender an application shell, + including an SPA fallback shell, provided the generated HTML does not contain + the measured table, UUID data, or detail content. Shell delivery may therefore + use the framework's normal static or server path, while all measured content + must be generated and rendered in the browser. - Next.js wraps the client-side rendered table in a `dynamic` import with - `ssr: false` to prevent build-time prerendering. -- TanStack Start, Nuxt, SvelteKit, and SolidStart disable SSR per route. + `ssr: false` to prevent the table from being rendered into the initial HTML. +- TanStack Start, Nuxt, and SvelteKit disable SSR for the benchmark routes. +- SvelteKit prerenders the main route's empty application shell so adapter-node + can serve it as a static asset. The dynamic detail route remains + non-prerendered because its IDs are generated in the browser. +- SolidStart uses `clientOnly` components for the table and detail content. - React Router uses route-level `clientLoader` functions with `HydrateFallback` so the client-rendered routes are not server-rendered. - Astro's benchmark table and detail components are React islands rendered with `client:only="react"`. Astro's `ClientRouter` is not used for this test because it changes navigation behavior rather than making components - client-only. Using `client:only` is often considered an anti pattern in Astro but needed to make the tests fair and measure just client side performance. -- We also chose React for Astro as its the currently the most popular. 23% of projects according to the Astro team as of writing this (15/07/2026). + client-only. Using `client:only` is generally discouraged for typical Astro + sites, but it keeps the measured content client-rendered for this comparison. +- The Astro islands use React because it was Astro's most popular UI framework + integration at the time this methodology was written, representing 23% of + projects according to the Astro team (15/07/2026). ### Server Side Rendered Tests
{entry.id} {entry.name}