Client-Side Rendering.
<div class="csr csr--loading" role="img" aria-label="CSR timeline, currently at loading">
<div class="csr__timeline">
<span class="csr__step csr__step--blank">Blank HTML<small>0ms</small></span>
<span class="csr__step csr__step--loading" style="--a:#BFD9F2;">Download 340KB JS<small>~600ms</small></span>
<span class="csr__step csr__step--rendered" style="--a:#BFD9F2;">Render + hydrate<small>~900ms</small></span>
</div>
<div class="csr__screen">
<span class="csr__preview csr__preview--blank"> </span>
<span class="csr__preview csr__preview--loading">◠</span>
<span class="csr__preview csr__preview--rendered" style="color:#BFD9F2;">Page content</span>
</div>
</div>
<style>
.csr { font: 12px/1.5 system-ui, sans-serif; color: #241430; background: #FAF7F2; padding: 16px; border-radius: 12px; width: 320px; box-sizing: border-box; }
.csr__timeline { display: flex; gap: 6px; margin-bottom: 14px; }
.csr__step { flex: 1; text-align: center; padding: 8px 4px; border-radius: 8px; background: #24143010; font-size: 10px; font-weight: 600; opacity: .4; }
.csr__step small { display: block; font-weight: 400; color: #24143099; margin-top: 3px; }
.csr--blank .csr__step--blank, .csr--loading .csr__step--loading, .csr--rendered .csr__step--rendered { opacity: 1; background: var(--a, #24143010); }
.csr__screen { height: 56px; display: grid; place-items: center; background: #ffffff; border: 1.5px dashed #24143020; border-radius: 8px; font-size: 20px; font-weight: 700; }
.csr__preview { display: none; }
.csr--blank .csr__preview--blank, .csr--loading .csr__preview--loading, .csr--rendered .csr__preview--rendered { display: block; }
@media (prefers-reduced-motion: no-preference) { .csr__preview--loading { animation: csr-spin 1s linear infinite; } }
@keyframes csr-spin { to { transform: rotate(360deg); } }
</style><div class="csrtw csrtw--loading font-sans text-[#241430] bg-[#FAF7F2] p-4 rounded-xl w-[320px] box-border" role="img" aria-label="CSR timeline, currently at loading">
<div class="flex gap-1.5 mb-3.5">
<span class="csrtw__step csrtw__step--blank flex-1 text-center py-2 px-1 rounded-lg bg-[#24143010] text-[10px] font-semibold opacity-40">Blank HTML<small class="block font-normal text-[#24143099] mt-0.5">0ms</small></span>
<span class="csrtw__step csrtw__step--loading flex-1 text-center py-2 px-1 rounded-lg bg-[#24143010] text-[10px] font-semibold opacity-40" style="--a:#BFD9F2;">Download 340KB JS<small class="block font-normal text-[#24143099] mt-0.5">~600ms</small></span>
<span class="csrtw__step csrtw__step--rendered flex-1 text-center py-2 px-1 rounded-lg bg-[#24143010] text-[10px] font-semibold opacity-40" style="--a:#BFD9F2;">Render + hydrate<small class="block font-normal text-[#24143099] mt-0.5">~900ms</small></span>
</div>
<div class="h-14 grid place-items-center bg-white border border-dashed border-[#24143020] rounded-lg text-xl font-bold">
<span class="csrtw__preview csrtw__preview--blank hidden"> </span>
<span class="csrtw__preview csrtw__preview--loading hidden">◠</span>
<span class="csrtw__preview csrtw__preview--rendered hidden" style="color:#BFD9F2;">Page content</span>
</div>
</div>
<style>
.csrtw--blank .csrtw__step--blank, .csrtw--loading .csrtw__step--loading, .csrtw--rendered .csrtw__step--rendered { opacity: 1; background: var(--a, #24143010); }
.csrtw--blank .csrtw__preview--blank, .csrtw--loading .csrtw__preview--loading, .csrtw--rendered .csrtw__preview--rendered { display: block; }
</style>export function Csr({
bundleSize = 340,
stage = "loading",
accent = "#BFD9F2",
showTimes = true,
}) {
const steps = [
{ id: "blank", label: "Blank HTML", time: "0ms" },
{ id: "loading", label: `Download ${bundleSize}KB JS`, time: "~600ms" },
{ id: "rendered", label: "Render + hydrate", time: "~900ms" },
];
const previews = { blank: "\u00A0", loading: "\u25F0", rendered: "Page content" };
return (
<div role="img" aria-label={`CSR timeline, currently at ${stage}`} style={{ font: "12px/1.5 system-ui, sans-serif", color: "#241430", background: "#FAF7F2", padding: 16, borderRadius: 12, width: 320, boxSizing: "border-box" }}>
<div style={{ display: "flex", gap: 6, marginBottom: 14 }}>
{steps.map((s) => (
<span key={s.id} style={{ flex: 1, textAlign: "center", padding: "8px 4px", borderRadius: 8, fontSize: 10, fontWeight: 600, opacity: s.id === stage ? 1 : .4, background: s.id === stage ? accent : "#24143010" }}>
{s.label}
{showTimes && <small style={{ display: "block", fontWeight: 400, color: "#24143099", marginTop: 3 }}>{s.time}</small>}
</span>
))}
</div>
<div style={{ height: 56, display: "grid", placeItems: "center", background: "#ffffff", border: "1.5px dashed #24143020", borderRadius: 8, fontSize: 20, fontWeight: 700, color: stage === "rendered" ? accent : "#241430" }}>{previews[stage]}</div>
</div>
);
}