Files
vercel__next.js/bench/basic-app/app/ui/post-grid.js
dan 7ffacec8ef Add more realistic bench fixtures (#95807)
This adds more fixtures modeled after Flight payloads from real sites:

- Vercel Dashboard-like (very app-y)
- React Docs-like after App Router conversion (mostly MDX site)
- Vercel Blog-like (passing down data from CMS)

(Actual data is randomly generated with a seed)

These are simplifications but I tried to incorporate the originals'
corresponding quirks. Such as Vercel Dashboard having many client
components; syntax highlight with many small spans in React Docs; Vercel
Blog currently shipping a load of data unnecessarily on the index page.
(The last one is not ideal but I think we should actually benchmark
"bad" cases like this too so I kept that.)


### Screenshots

<img width="1325" height="1021" alt="Screenshot 2026-07-15 at 04 29 43"
src="https://github.com/user-attachments/assets/62c80722-6fd5-4884-89db-106345f6412d"
/>

<img width="1360" height="994" alt="Screenshot 2026-07-15 at 04 30 21"
src="https://github.com/user-attachments/assets/0db93cd8-43ca-41df-b3e2-c422e0c94656"
/>

<img width="1368" height="991" alt="Screenshot 2026-07-15 at 04 30 43"
src="https://github.com/user-attachments/assets/9369e9be-12a4-4a1e-af32-01b6fdd4a457"
/>


### Comparison with real payloads

```
=== dashboard.html (fixture)  vs  p-overview.html (real)
  total KB                            208          637
  rows (model/I/T/other)       186/48/7/1 240/125/5/27
  byte share model/I/T %           92/4/4      36/62/2
  median row size B                   590          106
  max depth                            28           53
  mean depth                          8.4         10.5
  elements per KB(model)             14.9          5.2
  pure-data byte share %               24           21
  client-ref elements %                41           32
  objects:elements ratio             1.17         1.23
  avg props per element               1.7          3.1
  median children fanout                4            4
  string bytes %                       24           54
  median string len B                  10           15
  row refs per KB                     0.9          0.4
  encoded scalars per KB              3.5          1.2
  undefined markers                   347          740

=== docs.html (fixture)  vs  nextjs-docs.html (real)
  total KB                            261          487
  rows (model/I/T/other)        58/15/7/1   55/43/2/20
  byte share model/I/T %           93/1/6       94/6/0
  median row size B                   193          340
  max depth                            27           32
  mean depth                         21.9         12.3
  elements per KB(model)              0.8          0.8
  pure-data byte share %                8            9
  client-ref elements %                16           33
  objects:elements ratio             6.26         4.92
  avg props per element               1.7          2.8
  median children fanout                4            4
  string bytes %                       47           64
  median string len B                  17           35
  row refs per KB                     0.2          0.1
  encoded scalars per KB              7.5          2.9
  undefined markers                  1949         1377

=== blog.html (fixture)  vs  vercel-blog.html (real)
  total KB                            475          797
  rows (model/I/T/other)        41/15/0/1  102/53/7/14
  byte share model/I/T %          100/0/0       87/8/5
  median row size B                   426          505
  max depth                            34           44
  mean depth                          9.7         12.2
  elements per KB(model)              0.8          1.1
  pure-data byte share %               92           85
  client-ref elements %                23           33
  objects:elements ratio            28.92        16.39
  avg props per element               1.9          2.7
  median children fanout                3            4
  string bytes %                       46           48
  median string len B                   6            9
  row refs per KB                     0.1          0.1
  encoded scalars per KB              0.4          0.6
  undefined markers                   161          426
```

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 13:46:22 +02:00

103 lines
3.3 KiB
JavaScript

'use client'
import { fixtureName } from './vendor-fixtures'
import { describeUtils } from './vendor-util'
import { useState } from 'react'
import Avatar from './avatar'
import TagPill from './tag-pill'
import SavePost from './save-post'
function Cover({ cover }) {
return (
<div
className="cover"
role="img"
aria-label={cover.alt}
style={{
background: `linear-gradient(135deg, hsl(${cover.hueA} 70% 55%), hsl(${cover.hueB} 70% 40%))`,
}}
/>
)
}
// Minimal rich-text renderer for the CMS document shape, like the ones CMS
// SDKs ship. The index renders only the first paragraph as a preview; the
// full documents still travel with the posts, as list APIs return them.
function firstParagraph(doc) {
const p = doc.content.find((n) => n.nodeType === 'paragraph')
if (!p) return null
return p.content.map((run, i) => {
const text = run.value
if (run.marks.some((m) => m.type === 'bold'))
return <strong key={i}>{text}</strong>
if (run.marks.some((m) => m.type === 'italic'))
return <em key={i}>{text}</em>
return text
})
}
export default function PostGrid({ posts }) {
const [category, setCategory] = useState(null)
const shown = category
? posts.filter((p) => p.category.slug === category)
: posts
const cats = [
...new Map(posts.map((p) => [p.category.slug, p.category])).values(),
]
return (
<>
<div className="filter-bar">
<span className="label">
{category ? shown.length + ' posts in ' + category : 'All posts'}
</span>
{cats.map((c) => (
<button
key={c.slug}
type="button"
className={
'tag-pill' + (category === c.slug ? ' tag-pill-active' : '')
}
title={c.description}
onClick={() => setCategory(category === c.slug ? null : c.slug)}
>
{c.name}
</button>
))}
</div>
<div className="post-grid">
{shown.map((post) => (
<article key={post.id} className="post-card">
<Cover cover={post.cover} />
<div className="post-body">
<div className="post-tags flex items-center gap-2 truncate">
{post.tags.map((t) => (
<TagPill key={t} tag={t} />
))}
</div>
<h3 className="truncate text-sm font-medium">{post.title}</h3>
<p className="text-sm text-muted">
{firstParagraph(post.content)}
</p>
<div className="post-meta flex items-center gap-2 truncate text-xs text-muted">
<Avatar
name={post.author.name}
hue={post.author.avatarHue}
size={20}
/>
<span className="truncate">{post.author.name}</span>
<span>·</span>
<time dateTime={post.publishedAt}>{post.publishedAt}</time>
<span>·</span>
<span>{post.readingMinutes} min</span>
<span className="header-spacer" />
<SavePost slug={post.slug} />
</div>
</div>
</article>
))}
</div>
</>
)
}
export const __layers = [fixtureName, describeUtils].length