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)}
| {entry.id} |
{entry.name} |
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