모든 랩 앱(next-lab / start-lab / react-lab)이 공유하는 성능 계측 레이어. 데모 페이지마다 부착되어 단계(stage) 타임라인 · 단계별 화면 스냅샷 · 네트워크 프리셋 · RN WebView 전송을 제공한다.
{
"dependencies": {
"@renderlab/perf": "file:../../packages/perf",
"@renderlab/mock-data": "file:../../packages/mock-data"
}
}- npm이 로컬 디렉토리를 심링크로 설치한다. 소스(TS)를 그대로 배포하므로 앱 번들러가 트랜스파일해야 한다.
- Next:
next.config.ts에transpilePackages: ['@renderlab/perf', '@renderlab/mock-data'] - Vite 계열(react-lab, start-lab):
resolve: { dedupe: ['react', 'react-dom'] }+server.fs.allow에'../..'추가 (심링크 소스가 앱 루트 밖에 있음)
- Next:
| export | 종류 | 용도 |
|---|---|---|
PerfHUD |
클라이언트 컴포넌트 | 오버레이 HUD. 모든 페이지의 루트 레이아웃에 1개 부착. <PerfHUD app="next-lab" /> |
HydrationMarker |
클라이언트 컴포넌트 | 루트 레이아웃에 1개. js-eval(번들 평가 시작) + hydrated(hydration 완료) 기록 |
StageMark |
클라이언트 컴포넌트 | 마운트 시점을 단계로 기록. 지연 로딩 섹션의 "사용 가능 시점" 표시. <StageMark name="section-1" detail="추천 목록 hydrate 완료" /> |
StreamMark |
서버 렌더 가능 컴포넌트 | 인라인 <script>를 심어 HTML 파서 도달 시점(hydration 이전)을 기록 + 그 순간의 DOM 스냅샷 캡처. 스트리밍 SSR 데모 필수 |
STREAM_BOOTSTRAP |
문자열 상수 | StreamMark가 동작하려면 문서 <head> 최상단에 인라인 필요: <script dangerouslySetInnerHTML={{ __html: STREAM_BOOTSTRAP }} /> |
perfStage(name, {detail?, snapshot?}) |
함수 | 커스텀 단계 기록 (SSR 중엔 no-op). 기본으로 화면 스냅샷도 캡처 |
perfInit({app?, captureSnapshots?}) |
함수 | 자동 계측 초기화 (멱등). PerfHUD가 호출하므로 보통 불필요 |
getApiDelay() |
함수 | ?apiDelay= 쿼리(0~15000 클램프)를 읽음. 클라이언트 fetch 시 이 값을 API에 전달할 것 |
getPerfEntries / getPerfReport / subscribePerf / perfPrint |
함수 | HUD 외부에서 데이터 접근이 필요할 때 |
nav-start, ttfb, fcp, lcp(갱신형), dom-content-loaded, load,
js-eval, hydrated(HydrationMarker), long-tasks(갱신형), worst-interaction(갱신형, INP 근사), stream:<이름>(StreamMark)
| 이름 | 의미 |
|---|---|
data-requested |
클라이언트에서 데이터 요청 시작 |
data-received |
데이터 응답 수신 |
content-rendered |
주 콘텐츠가 화면에 렌더됨 (커밋 후 effect에서 기록) |
section-1 ~ section-N |
스트리밍/지연 섹션 각각의 완료 (StreamMark는 stream:section-N으로 찍힘) |
| 데모 고유 단계 | editor-requested, editor-ready 등 — 쌍끼리 동일하게 |
import { PerfHUD, HydrationMarker, STREAM_BOOTSTRAP } from '@renderlab/perf'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ko">
<head>
<script dangerouslySetInnerHTML={{ __html: STREAM_BOOTSTRAP }} />
</head>
<body>
{children}
<HydrationMarker />
<PerfHUD app="next-lab" />
</body>
</html>
)
}루트 문서의 <head>에 STREAM_BOOTSTRAP 인라인 스크립트, <body> 말미에 <HydrationMarker />와 <PerfHUD app="start-lab" />.
import { PerfHUD, HydrationMarker } from '@renderlab/perf'
// 루트 컴포넌트 트리에 <HydrationMarker />와 <PerfHUD app="react-lab" /> 포함?apiDelay=쿼리 — HUD 프리셋 버튼(0/200/800/2000ms)이 조절. 데이터 응답만 지연. 서버 코드는 자체 searchParams에서, 클라이언트 코드는getApiDelay()로 읽어 API/데이터 함수의delay로 전달한다.- DevTools Network 스로틀 — 브라우저에서 전체 회선 재현.
npm run throttle프록시 (tools/throttle-proxy) — DevTools가 없는 RN WebView까지 포함해 전체 회선(HTML/JS/CSS/API)을 지연+대역폭 제한. 프로파일: wifi/4g/fast3g/slow3g/2g.
window.ReactNativeWebView가 존재하면 단계가 갱신될 때마다(250ms 스로틀) 아래 JSON을 postMessage한다.
load 완료 1.2초 뒤 final: true로 마지막 전송.
{
"type": "renderlab-perf",
"app": "next-lab",
"url": "/csr-vs-ssr/as-is?apiDelay=800",
"apiDelay": 800,
"final": false,
"entries": [{ "name": "ttfb", "t": 132.4, "detail": "..." }]
}- 스냅샷은 script/HUD 제거 후의 DOM 직렬화이며, 캡처는 rAF 2회 뒤(페인트 반영 후)에 수행된다.
- DOM 노드 6,000개 초과 또는 800KB 초과 시 측정 오염 방지를 위해 캡처를 생략한다 (HUD에 📷 없음).
- JS 실행 전 단계(순수 HTML 도착 등)는
StreamMark(인라인 스크립트)로만 캡처 가능하다. - 스냅샷 캡처 자체가 수 ms의 오버헤드를 만든다. 정밀 측정이 필요하면
perfInit({ captureSnapshots: false }).