CLI that gives agents what humans get from React DevTools and the Next.js dev overlay — component trees, props, hooks, PPR shells, errors, network — as shell commands that return structured text.
If next-browser is not already on PATH, install @vercel/next-browser
globally with the user's package manager, then playwright install chromium.
If next-browser is already installed, it may be outdated. Run
next-browser --version and compare against the latest on npm
(npm view @vercel/next-browser version). If the installed version is
behind, upgrade it (npm install -g @vercel/next-browser@latest or the
equivalent for the user's package manager) before proceeding.
If the project's Next.js version is v16.2.0-canary.37 or later, bundled
docs live at node_modules/next/dist/docs/. Before doing PPR work, Cache
Components work, or any non-trivial Next.js task, read the relevant doc there
— your training data may be outdated. The bundled docs are the source of truth.
See https://nextjs.org/docs/app/guides/ai-agents for background.
open and go.[{"name":"session","value":"..."}], or (2) just
"Copy as cURL" from DevTools → Network on any authenticated request —
you can extract the cookies from the header yourself.project, config reads) before being asked.screenshot after every navigation, code change, or visual finding.
Always caption it (screenshot "Before fix", screenshot "PPR shell — locked").
In headed mode the Screenshot Log window opens automatically so the user
sees every screenshot in real time.By default the browser opens headed (visible window). For CI or cloud
environments with no display, set NEXT_BROWSER_HEADLESS=1 to run
headless.
open <url> [--cookies-json <file>]Launch browser, navigate to URL. With --cookies-json, sets auth cookies
before navigating (domain derived from URL hostname).
$ next-browser open http://localhost:3024/vercel --cookies-json cookies.json
opened → http://localhost:3024/vercel (11 cookies for localhost)
Cookie file format: [{"name":"authorization","value":"Bearer ..."}, ...]
Only name and value are required per cookie — omit domain, path,
expires, etc. To create the file, use Bash (echo '[...]' > /tmp/cookies.json)
since the Write tool requires a prior Read.
closeClose browser and kill daemon.
goto <url>Navigate to a URL with a fresh server render. The browser loads a new document — equivalent to typing a URL in the address bar.
$ next-browser goto http://localhost:3024/vercel/~/deployments
→ http://localhost:3024/vercel/~/deployments
push [path]Client-side navigation — the page transitions without a full reload, the way a user clicks a link in the app. Without a path, shows an interactive picker of all links on the current page.
$ next-browser push /vercel/~/deployments
→ http://localhost:3024/vercel/~/deployments
If push fails silently (URL unchanged), the route wasn't prefetched.
backGo back one page in browser history.
reloadReload the current page from the server.
ssr lockBlock external scripts on all subsequent navigations. While locked, every
goto, push, back, and reload shows the raw server-rendered HTML
without React hydration or client-side JavaScript — what search engines
and social crawlers see.
$ next-browser ssr lock
ssr locked — external scripts blocked on all navigations
ssr unlockRe-enable external scripts. The next navigation will load normally with full hydration.
$ next-browser ssr unlock
ssr unlocked — external scripts re-enabled
perf [url]Profile a full page load — reloads the current page (or navigates to a URL) and collects Core Web Vitals and React hydration timing in one pass.
$ next-browser perf http://localhost:3000/dashboard
# Page Load Profile — http://localhost:3000/dashboard
## Core Web Vitals
TTFB 42ms
LCP 1205.3ms (img: /_next/image?url=...)
CLS 0.03
## React Hydration — 65.5ms (466.2ms → 531.7ms)
Hydrated 65.5ms (466.2 → 531.7)
Commit 2.0ms (531.7 → 533.7)
Waiting for Paint 3.0ms (533.7 → 536.7)
Remaining Effects 4.1ms (536.7 → 540.8)
## Hydrated components (42 total, sorted by duration)
DeploymentsProvider 8.3ms
NavigationProvider 5.1ms
...
TTFB — server response time (Navigation Timing API).
LCP — when the largest visible element painted, plus what it was.
CLS — cumulative layout shift score (lower is better).
Hydration — React reconciler phases and per-component cost (requires
React profiling build / next dev; production strips console.timeStamp).
Without a URL, reloads the current page. With a URL, navigates there first.
renders startBegin recording React re-renders. Hooks into onCommitFiberRoot to
collect raw per-component data: render count, totalTime, selfTime,
DOM mutations, change reasons, and FPS.
Survives full-page navigations (goto/reload) and captures mount
and hydration renders — no need to start before or after navigation.
$ next-browser renders start
recording renders — interact with the page, then run `renders stop`
renders stop [--json]Stop recording and print a per-component render profile. Raw data — the agent decides what's actionable.
$ next-browser renders stop
# Render Profile — 3.05s recording
# 426 renders (38 mounts + 388 re-renders) across 38 components
# FPS: avg 120, min 106, max 137, drops (<30fps): 0
## Components by total render time
| Component | Insts | Mounts | Re-renders | Total | Self | DOM | Top change reason |
| ---------------------- | ----- | ------ | ---------- | -------- | -------- | ----- | -------------------------- |
| Parent | 1 | 1 | 9 | 5.8ms | 3.4ms | 10/10 | state (hook #0) |
| MemoChild | 3 | 3 | 27 | 2ms | 1.9ms | 30/30 | props.data |
| Router | 1 | 1 | 9 | 6.3ms | — | 0/10 | parent (ErrorBoundaryHandler) |
## Change details (prev → next)
Parent
state (hook #0): 0 → 1
MemoChild
props.data: {value} → {value}
The Change details section shows the actual prev→next values for
each change. This makes the data self-contained — you can see that
MemoChild gets props.data: {value} → {value} (same shape, new
reference — memo defeated) without needing to inspect the component.
With --json, outputs raw structured JSON with full change arrays
per component (type, name, prev, next for each render event).
Columns:
Insts — number of unique component instances observed during recordingMounts — how many times an instance mounted (first render, no alternate fiber)Re-renders — update-phase renders (total renders minus mounts)Total — inclusive render time (component + children)Self — exclusive render time (component only, excludes children)DOM — how many renders actually mutated the DOM vs total rendersTop change reason — most frequent trigger for this componentTiming data (Total, Self) requires a React profiling build
(next dev). In production builds these columns show — but render
counts, DOM mutations, and change reasons are still reported.
Change reasons — what triggered each re-render:
props.<name> — a prop changed by reference, with prev→next valuesstate (hook #N) — a useState/useReducer hook changed, with prev→next valuescontext (<name>) — a specific context changed, with prev→next valuesparent (<name>) — parent component re-rendered, names the parentparent (<name> (mount)) — parent is also mounting (typical during page load, not a leak)mount — first renderFPS — frames per second during recording. drops counts frames
below 30fps.
Up to 200 components are tracked. If output exceeds 4 000 chars it is written to a temp file.
restart-serverRestart the Next.js dev server and clear its caches. Forces a clean recompile from scratch.
Last resort. HMR picks up code changes on its own — reach for this only when you have evidence the dev server is wedged (stale output after edits, builds that never finish, errors that don't clear).
Often exits with net::ERR_ABORTED — this is expected (the page detaches
during restart). Follow up with goto <url> to re-navigate after the
server is back. Don't treat this error as a failure.
ppr lockPrerequisite: PPR requires cacheComponents to be enabled in
next.config. Without it the shell won't have pre-rendered content to show.
Freeze dynamic content so you can inspect the static shell — the part of the page that's instantly available before any data loads. After locking:
goto — shows the server-rendered shell with holes where dynamic
content would appear.push — shows what the client already has from prefetching. Requires
the current page to already be hydrated (prefetch is client-side),
so lock after you've landed on the origin, not before.$ next-browser ppr lock
locked
ppr unlockResume dynamic content and print a shell analysis — which Suspense
boundaries were holes in the shell, what blocked them, and which were
static. The output can be very large (hundreds of boundaries). Pipe
through | head -20 if you only need the summary and dynamic holes.
$ next-browser ppr unlock
unlocked
# PPR Shell Analysis
# 131 boundaries: 3 dynamic holes, 128 static
## Summary
- Top actionable hole: TrackedSuspense — usePathname (client-hook)
- Suggested next step: This route segment is suspending on client hooks. Check loading.tsx first...
- Most common root cause: usePathname (client-hook) affecting 1 boundary
## Quick Reference
| Boundary | Type | Fallback source | Primary blocker | Source | Suggested next step |
| --- | --- | --- | --- | --- | --- |
| TrackedSuspense | component | unknown | usePathname (client-hook) | tracked-suspense.js:6 | Push the hook-using cl... |
| TeamDeploymentsLayout | route-segment | unknown | unknown | layout.tsx:37 | Inspect the nearest us... |
| Next.Metadata | component | unknown | unknown | unknown | No primary blocker was... |
## Detail
TrackedSuspense
rendered by: TrackedSuspense > RootLayout > AppLayout
environments: SSR
TeamDeploymentsLayout
suspenders unknown: thrown Promise (library using throw instead of use())
## Static (pre-rendered in shell)
GeistProvider at .../geist-provider.tsx:80:9
TrackedSuspense at ...
...
The Quick Reference table is the main overview — boundary, blocker, source, and suggested fix at a glance. The Detail section only appears for holes that have extra info (owner chains, environments, secondary blockers) not already in the table.
errors doesn't report while locked. If the shell looks wrong (empty,
bailed to CSR), unlock and goto the page normally, then run errors.
Don't debug blind under the lock.
Full bailout (scrollHeight = 0). When PPR bails out completely, unlock
returns just "unlocked" with no shell analysis — there are no boundaries to
report. In this case, unlock, goto the page normally, then use errors
and logs to find the root cause.
treeFull React component tree — every component on the page with its hierarchy, like the Components panel in React DevTools.
$ next-browser tree
# React component tree
# Columns: depth id parent name [key=...]
# Use `tree <id>` for props/hooks/state. IDs valid until next navigation.
0 38167 - Root
1 38168 38167 HeadManagerContext.Provider
2 38169 38168 Root
...
224 46375 46374 DeploymentsProvider
226 46506 46376 DeploymentsTable
tree <id>Inspect one component: ancestor path, props, hooks, state, source location (source-mapped to original file).
$ next-browser tree 46375