JavaScript is disabled. Some features may not work.
next-browser — Install Guide | SkillsNav
🇺🇸 English🇨🇳 中文
SkillsNav
Home

next-browser

frameworkSafeIntermediateClaude

How to Install

Claude Code:
git clone --depth 1 https://github.com/hyecompany/ararat-web.git && cp ararat-web/.agents/skills/next-browser ~/.claude/skills/next-browser -r
--- name: next-browser description: >- 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. --- # next-browser 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. --- ## Next.js docs awareness 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. --- ## Working with the user ### Onboarding - If the user already gave a URL, cookies, and task — skip questions, `open` and go. - Otherwise ask only what's missing: dev server URL (running?), session cookies if behind login. - For cookies, give the user two options: (1) DevTools → Application → Cookies, export as `[{"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. - Never say "ready, what would you like to do?". Never auto-discover (port scans, `project`, config reads) before being asked. ### Show, don't tell - `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. - Don't narrate what a screenshot shows. State your conclusion or next action. ### Escalate, don't decide - Suspense boundary placement and fallback UI — design with the user. - Caching decisions (staleness, visibility) — the user's call, not yours. - "Make this page faster" without context — ask: cold URL hit or client navigation? From which page? Don't guess, don't do both. --- ## Headless mode By default the browser opens headed (visible window). For CI or cloud environments with no display, set `NEXT_BROWSER_HEADLESS=1` to run headless. --- ## Commands ### `open [--cookies-json ]` 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. ### `close` Close browser and kill daemon. --- ### `goto ` 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. ### `back` Go back one page in browser history. ### `reload` Reload the current page from the server. ### `ssr lock` Block 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 unlock` Re-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 start` Begin 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 recording - `Mounts` — 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 renders - `Top change reason` — most frequent trigger for this component **Timing 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.` — a prop changed by reference, with prev→next values - `state (hook #N)` — a useState/useReducer hook changed, with prev→next values - `context ()` — a specific context changed, with prev→next values - `parent ()` — parent component re-rendered, names the parent - `parent ( (mount))` — parent is also mounting (typical during page load, not a leak) - `mount` — first render **FPS** — 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-server` Restart 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 ` to re-navigate after the server is back. Don't treat this error as a failure. --- ### `ppr lock` **Prerequisite:** 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 unlock` Resume 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. --- ### `tree` Full 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 ` 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 ` Inspect one component: ancestor path, props, hooks, state, source location (source-mapped to original file). ``` $ next-browser tree 46375 path: Root > ... > Prerender(TeamDeploymentsPage) > Prerender(FullHeading) > Prerender(TrackedSuspense) > Suspense > DeploymentsProvider DeploymentsProvider #46375 props: children: [, , , , ] hooks: IsMobile: undefined (1 sub) Router: undefined (2 sub) DeploymentListScope: undefined (1 sub) User: undefined (4 sub) Team: undefined (4 sub) ... DeploymentsInfinite: undefined (12 sub) source: app/(dashboard)/[teamSlug]/(team)/~/deployments/_parts/context.tsx:180:10 ``` IDs are valid until navigation. Re-run `tree` after `goto`/`push`. --- ### `viewport [WxH]` Show or set the browser viewport size. Useful for testing responsive layouts. ``` $ next-browser viewport 1440x900 $ next-browser viewport 375x812 viewport set to 375x812 ``` Once set, the viewport stays fixed across navigations. `window.resizeTo()` via `eval` is a no-op in Playwright — always use this command to change dimensions. --- ### `screenshot [caption] [--full-page]` Behavioral rules are in **Working with the user → Show, don't tell**. Use `screenshot` only when visual layout matters (CSS, appearance, PPR shell). For page content or deciding what to click, use `snapshot`. Captures the viewport (or full scrollable page with `--full-page`) to a temp PNG file and returns the path. In headed mode, every screenshot is added to the **Screenshot Log** — a live browser window that accumulates all screenshots taken during the session. In headless mode the log window is skipped. The optional caption describes the screenshot or the rationale for taking it. Captions appear in the Screenshot Log above each image. ``` $ next-browser screenshot "Homepage after login" /tmp/next-browser-1711234567890.png $ next-browser screenshot "Full page layout" --full-page /tmp/next-browser-1711234567891.png ``` ### `snapshot` Snapshot the page's accessibility tree — the semantic structure a screen reader sees — with `[ref=eN]` markers on every interactive element. Use this to discover what's on the page before clicking. ``` $ next-browser snapshot - navigation "Main" - link "Home" [ref=e0] - link "Dashboard" [ref=e1] - main - heading "Settings" - tablist - tab "General" [ref=e2] (selected) - tab "Security" [ref=e3] - region "Profile" - textbox "Username" [ref=e4] - button "Save" [ref=e5] ``` The tree shows headings, landmarks (`navigation`, `main`, `region`), and state (`selected`, `checked`, `expanded`, `disabled`) so you understand page layout, not just a flat element list. Refs are ephemeral — they reset on every `snapshot` call and are invalid after navigation. Re-run `snapshot` after `goto`/`push`. ### `click ` Click an element using real pointer events (`pointerdown → mousedown → pointerup → mouseup → click`). This works with libraries that ignore synthetic `.click()` (Radix UI, Headless UI, etc.). Three ways to target: | Input | Example | How it resolves | | --- | --- | --- | | Ref from tree | `click e3` | Looks up role+name from last snapshot | | Plain text | `click "Security"` | Playwright `text=Security` selector | | Playwright selector | `click "#submit-btn"` | Used as-is (CSS, `role=`, etc.) | **Recommended workflow:** run `snapshot` first, then `click eN`. Refs are the most reliable — they resolve via ARIA role+name, so they work even when elements have no stable CSS selector. **Clicking navigation links can timeout.** `click` on a Next.js `` waits for the navigation to settle, which can exceed the command timeout. If `click` hangs on a nav link, cancel it and use `goto ` instead. ``` $ next-browser snapshot - tablist - tab "General" [ref=e0] (selected) - tab "Security" [ref=e1] $ next-browser click e1 clicked $ next-browser snapshot - tablist - tab "General" [ref=e0] - tab "Security" [ref=e1] (selected) ``` ### `fill ` Fill a text input or textarea. Clears existing content, then types the new value — dispatches all the events React and other frameworks expect. ``` $ next-browser snapshot - textbox "Username" [ref=e4] $ next-browser fill e4 "judegao" filled ``` ### `eval [ref]