name: profile-browsing description: Profile app performance while browsing, collecting Web Vitals and React rerender data via react-scan. Orchestrates parallel profiler subagents via playwright-cli to capture navigation timing, long tasks, layout shifts, LCP, React commit counts, render bursts, and per-component render data. Use when profiling browsing performance, finding bottlenecks, diagnosing excessive rerenders, or auditing page performance.
Profile Browsing Performance
Two-layer profiling: browser-level symptoms (Web Vitals, long tasks, scroll jank) and React-level diagnosis (commit counts, render bursts, per-component render data from react-scan). Each profiler subagent runs in its own browser session and context window.
Prerequisites
- Dev server running at https://5chan.localhost (
yarn startvia Portless) playwright-cliinstalled (npm install -g @playwright/cli@latest)
IMPORTANT: The orchestrator (you) is responsible for ensuring exactly ONE dev server is running. Profiler subagents must NEVER start a dev server themselves.
react-scan (already configured)
The app has react-scan set up in src/lib/react-scan.ts with report: true. In dev mode it:
- Highlights rerendering components visually (toolbar + overlay)
- Tracks per-component render counts and times internally
- Exposes
window.__getReactScanReport()for programmatic collection
The profiler's addInitScript sets window.__PROFILING__ = true before the app loads, which tells react-scan to disable its toolbar and sounds during automated runs.
No additional setup needed — react-scan is already a devDependency and imported in the entry file.
Step 0: Ensure Dev Server is Running
Before spawning any profiler subagents, verify exactly one dev server is available:
# Check if the dev server is reachable
curl -sf https://5chan.localhost -o /dev/null && echo "OK" || echo "NOT RUNNING"
- If OK: proceed to Step 1.
- If NOT RUNNING: start one instance with
yarn start(backgrounded), then poll until it responds. Do NOT start more than one. - If a dev server is already running on a different port (check
ps aux | grep vite), reuse it — do not start another.
Step 1: Define Route Batches
Split routes into batches of 2–4 for parallel profiling.
Default batches (adjust boards as needed):
| Batch | Session | Routes | Focus |
|---|---|---|---|
| 1 | prof-1 | /all, /all/catalog | Multi-board feed + catalog |
| 2 | prof-2 | /biz, /biz/catalog | Single board feed + catalog |
| 3 | prof-3 | /pol, /pol/catalog, /g, /g/catalog | Board switching (feed reloads) |
Keep batches balanced. Add thread views (/:boardIdentifier/thread/:cid) as needed.
Step 2: Spawn Profiler Subagents
Read the profiler subagent definition at .cursor/agents/profiler.md. Then spawn one profiler Task per batch in parallel (single message, multiple Task calls):
For each batch, create a Task:
subagent_type: "profiler"
prompt: |
Session name: "prof-N"
Routes to profile: /route1, /route2, ...
Any non-default app URL or extra profiling constraints
Spawn up to 4 subagents simultaneously. Each opens its own browser session, navigates routes, scrolls, collects both Web Vitals and react-scan data per route, and returns a structured issues list.
Trade-off: Parallel is faster but may skew timing results under heavy machine load. For precise measurements, spawn sequentially.
Step 3: Merge Results
Collect structured output from each subagent and merge:
- Concatenate all Critical / Warning / React Rerenders / Scroll Jank / Info items
- Combine per-view summary tables into one
- Merge react-scan component data across routes (same component appearing in multiple routes = sum counts)
- Deduplicate shared issues (e.g., same slow resource across routes)
- Sort by severity (Critical first)
Step 4: Final Report
## Performance Profile Results
### Critical
- [metric]: [value] at [route] — [what likely needs fixing]
### Warning
- [metric]: [value] at [route] — [what likely needs fixing]
### React Rerenders
- [route]: [N] commits during load, [M] during scroll — [likely cause]
- Render bursts detected at [routes] — suggests cascading state updates
- Top rerendering components (react-scan):
- [ComponentName]: [total count] renders across [routes], [time]ms total
- [ComponentName]: [total count] renders across [routes], [time]ms total
### Scroll Jank
- [route]: [N] long tasks during scroll (max [X]ms), [M] React commits — [likely cause]
### Info
- [observations]
### Per-View Summary
| View | Nav (ms) | Long Tasks | CLS | LCP (ms) | Commits | Scroll Commits | Bursts | Top Component |
|------|----------|-----------|-----|-----------|---------|----------------|--------|---------------|
| /all | ... | ... | ... | ... | ... | ... | ... | ... |
Interpreting React Metrics
| Signal | Likely cause | Fix direction |
|---|---|---|
| High commits, no long tasks | Frequent cheap rerenders | React.memo, stabilize props |
| High commits + long tasks | Expensive rerenders | Profile render cost, split components |
| High scroll commits | Scroll/intersection observer triggering renders | Throttle handlers, memoize list items |
| Render bursts (>5 in 100ms) | Cascading state updates | Batch updates, review Zustand selectors |
| react-scan: component with >30 renders | Missing memoization or unstable references | useMemo/useCallback, check parent renders |
| react-scan: component with >50ms time | Expensive render function | Split component, move work out of render |
Element-source follow-up
When react-scan identifies a rerender hotspot but you still need the exact file behind a concrete DOM node, hand off to $inspect-elements.
playwright-cli -s=prof-followup eval "async el => JSON.stringify(await window.__ELEMENT_SOURCE__.resolve(el))" e7
Use source.filePath as the direct edit target and stack to understand which parent components own the node.
Step 5: Cleanup
After profiling is complete and the report is delivered, verify no orphaned processes were left behind:
# Check for any Vite dev servers started during profiling
ps aux | grep 'vite.*--port' | grep -v grep
- If the dev server was already running before Step 0, leave it alone.
- If the orchestrator started the dev server in Step 0, kill it now.
- If there are multiple Vite processes (should never happen), kill the extras and warn the user.
Also close any leftover playwright-cli sessions:
# Close any profiling sessions that weren't properly closed
playwright-cli -s=prof-1 close 2>/dev/null
playwright-cli -s=prof-2 close 2>/dev/null
playwright-cli -s=prof-3 close 2>/dev/null
Notes
- Session isolation: Each subagent uses a named playwright-cli session (
-s=prof-N). - Context isolation: Each subagent runs in its own context window.
- Per-route collection: Data resets on each
goto— the profiler collects before navigating away. - addInitScript persistence: Instrumentation re-injects automatically in each new document.
- Tracing: Each subagent produces a
trace.zipviewable in Trace Viewer. - Board codes:
biz,pol,g,a,v, etc. map to community addresses via the directory. - Without react-scan: If
__getReactScanReportreturns null, the profiler falls back to commit counts + render bursts (still useful, just no component names).