mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
7ffacec8ef
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>
103 lines
3.3 KiB
JavaScript
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
|