Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
514 changes: 89 additions & 425 deletions packages/docs/src/components/DependencyStats.astro

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions packages/docs/src/components/Description.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---

---

<p class="description">
<slot />
</p>

<style>
.description {
font-size: 16px;
color: var(--ft-muted);
line-height: 1.6;
margin-bottom: 1em;
max-width: 700px;
}

@media screen and (max-width: 768px) {
.description {
font-size: 15px;
}
}
</style>
32 changes: 32 additions & 0 deletions packages/docs/src/components/DetailsLink.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
interface Props {
href: string
label: string
}

const { href, label } = Astro.props
---

<p class="details-link">
For full details and methodology, see <a href={href}>{label}</a>.
</p>

<style>
.details-link {
font-size: 13px;
color: var(--ft-muted);
margin-bottom: 1em;
}

.details-link a {
color: var(--ft-accent);
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
}

.details-link a:hover {
color: var(--ft-accent-hover);
text-decoration: underline;
}
</style>
153 changes: 8 additions & 145 deletions packages/docs/src/components/FrameworkDetail.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
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'
import SSRStatsMethodologyNotes from './SSRStatsMethodologyNotes.astro'

interface Props {
devtime: CollectionEntry<'devtime'>['data']
Expand Down Expand Up @@ -43,10 +46,10 @@ const measuredDateDisplay = (() => {

<section class="section">
<h2>Dev Time Performance</h2>
<p class="methodology">
<MethodologyTag>
Measured using pnpm on GitHub Actions (ubuntu-latest, Node 24) based on
the starter project set up by each framework's CLI.
</p>
</MethodologyTag>

<div class="table-wrapper">
<table aria-label="Dependency counts">
Expand Down Expand Up @@ -130,10 +133,10 @@ const measuredDateDisplay = (() => {
<section class="section">
<h2>Runtime Performance</h2>
<h3>SSR Performance</h3>
<p class="methodology">
<MethodologyTag>
Measured on GitHub Actions (ubuntu-latest, Node 24) using custom SSR
benchmark apps.
</p>
</MethodologyTag>
<div class="table-wrapper">
<table aria-label="SSR performance">
<thead>
Expand Down Expand Up @@ -165,50 +168,7 @@ const measuredDateDisplay = (() => {
</tbody>
</table>
</div>

<div class="methodology-notes">
<h4>Methodology</h4>
<ul>
<li>
Each framework renders a table of 1000 rows with two UUID columns
</li>
<li>
Mock HTTP requests bypass TCP overhead for accurate rendering
measurement
</li>
<li>
Data is loaded asynchronously to simulate real-world data fetching
</li>
<li>
Duplication factor indicates how many times each UUID appears in
the response (1x = optimal, 2x = includes hydration payload)
</li>
<li>
Benchmarks run for 10 seconds using{' '}
<a
href="https://github.com/tinylibs/tinybench"
target="_blank"
rel="noopener noreferrer"
>
tinybench
</a>
</li>
<li>
React Router uses Web APIs internally; benchmarks include the cost
of its official Node.js adapter (<code>@react-router/node</code>)
</li>
<li>
Inspired by{' '}
<a
href="https://github.com/eknkc/ssr-benchmark"
target="_blank"
rel="noopener noreferrer"
>
eknkc/ssr-benchmark
</a>
</li>
</ul>
</div>
<SSRStatsMethodologyNotes />
</section>
)
}
Expand Down Expand Up @@ -287,12 +247,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;
Expand All @@ -309,45 +263,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;
Expand Down Expand Up @@ -410,62 +325,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;
Expand Down
68 changes: 68 additions & 0 deletions packages/docs/src/components/MethodologyNotes.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---

---

<div class="methodology-notes">
<h4>Methodology</h4>
<ul>
<slot />
</ul>
</div>

<style>
/* Elements owned by this component — regular scoped styles */
.methodology-notes {
margin-top: 1.5em;
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);
}

/* Slotted content — escape scoping, anchored to .methodology-notes */
:global(.methodology-notes li) {
margin-bottom: 0.4em;
}

:global(.methodology-notes li:last-child) {
margin-bottom: 0;
}

:global(.methodology-notes a) {
color: var(--ft-accent);
text-decoration: none;
}

:global(.methodology-notes a:hover) {
color: var(--ft-accent-hover);
text-decoration: underline;
}

/* Dark mode */
: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);
}
</style>
15 changes: 15 additions & 0 deletions packages/docs/src/components/MethodologyTag.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---

---

<p class="methodology">
<slot />
</p>

<style>
.methodology {
font-size: 14px;
color: var(--ft-muted);
margin-bottom: 0.5em;
}
</style>
43 changes: 43 additions & 0 deletions packages/docs/src/components/SSRStatsMethodologyNotes.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
import MethodologyNotes from '../components/MethodologyNotes.astro'
---

<MethodologyNotes>
<li>Each framework renders a table of 1000 rows with two UUID columns</li>
<li>
Mock HTTP requests bypass TCP overhead for accurate rendering measurement
</li>
<li>Data is loaded asynchronously to simulate real-world data fetching</li>
<li>
Duplication factor indicates how many times each UUID appears in the
response (1x = optimal, 2x = includes hydration payload)
</li>
<li>
Benchmarks run for 10 seconds using <a
href="https://github.com/tinylibs/tinybench"
target="_blank"
rel="noopener noreferrer">tinybench</a
>
</li>
<li>
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 (<code
>@react-router/node</code
>, h3, and srvx respectively)
</li>
<li>
Next.js defaults to React Server Components (RSC), a different rendering
model than traditional SSR. To keep the comparison fair, Next.js uses <code
>"use client"</code
> to opt out of RSC and use traditional SSR + hydration like most of the other
frameworks
</li>
<li>
Inspired by <a
href="https://github.com/eknkc/ssr-benchmark"
target="_blank"
rel="noopener noreferrer">eknkc/ssr-benchmark</a
>
</li>
</MethodologyNotes>
Loading
Loading