---
name: nextcanvas
version: 0.1.1
description: Edit a locally-running Next.js App Router app from the browser — double-click text, and the change is written back to the source file. Dev-only.
homepage: https://nextcanvas.rishithakkar.com
user-invocable: true
---
# nextcanvas — setup guide for agents
This skill is **doc-only**. There is no API and no MCP server. It documents how to
install nextcanvas into a Next.js project and what it can edit once running.
nextcanvas is a **dev-only** tool for the **Next.js App Router**. In `next dev`,
double-clicking static text in the browser writes the change back into the real
source file via a formatting-preserving AST edit, and Fast Refresh re-renders it.
In a production build it compiles out completely.
## Requirements
- Next.js **16.2+**, App Router
- React 18+
- A local `next dev` session (webpack or Turbopack; on Windows prefer `next dev --webpack`)
## Setup
```
npm i -D @rishi-thak/nextcanvas
npx nextcanvas init
```
Keep it in **devDependencies**. `init` is idempotent — re-running is safe.
`init` does two things:
1. Wraps the exported config in `next.config.*` with `withCanvas()`.
2. Mounts `` in the root layout, guarded by `NODE_ENV`.
### Manual wiring
If `init` cannot patch the files, apply both edits by hand.
`next.config.js`:
```js
const { withCanvas } = require('@rishi-thak/nextcanvas/next');
module.exports = withCanvas({
// your existing config
});
```
`app/layout.tsx`:
```tsx
import { NextCanvasOverlay } from '@rishi-thak/nextcanvas';
export default function RootLayout({ children }) {
return (
{children}
{process.env.NODE_ENV === 'development' && }
);
}
```
### Verify
Run `next dev`, load the app, and confirm the nextcanvas toolbar appears in the
corner. Double-click a headline, type, press Enter, and check the source file
changed on disk.
## What you can edit
| Kind | Example | Notes |
|---|---|---|
| Static text | `Hello
` | Also text mixed with inline tags: `Hi there
` |
| Component text | `Title`, `` | Plain-identifier components and one-level member tags |
| Bound text | `{speaker.name}
` | Member chains, `{a ?? b}`, string-literal ternaries, `.map` params, component props |
| Attributes | `href`, `src`, `alt`, `title`, `placeholder`, `aria-label` | Literal values, and bound identifiers like `href={GITHUB}` |
| Inline styles | `style={{ color: 'red' }}` | Colour, size, weight, alignment, padding |
Not editable: computed access (`{items[i].x}`), call results (`{fn().y}`), text
mixed with an expression (`Hi {name}`), namespaced tags, and `className` /
Tailwind classes.
## How it works
A compile-time SWC plugin stamps `data-loc="::"` onto elements
that have something editable. The browser overlay reads that stamp and POSTs the
change to a local write-back server, which applies it with ts-morph and saves.
Fast Refresh does the rest — there is no socket to maintain.
## Notes and gotchas
- `withCanvas` boots the write-back server on **port 3131** in dev. Override with
`NEXTCANVAS_PORT`; that single variable also reaches the browser.
- The tool is a **complete no-op** when `NODE_ENV` is not `development`. Nothing
is stamped and no overlay script is served in a production build.
- The toolbar has a **Buttons** toggle. Off (the default) makes the page inert so
single-click selects an element for styling instead of triggering links and
handlers. Turn it on to use the app normally.
- If a component **transforms** its children (uppercases them, wraps them), the
DOM text will not match the source and the edit is rejected rather than
guessed at.
- Bound text is matched **by value**, not by position, because a `.map` is
routinely filtered and reordered. A value shared by several entries is
ambiguous, so that edit is refused instead of risking the wrong one.
## Links
- Docs: https://nextcanvas.rishithakkar.com/docs
- Package: https://www.npmjs.com/package/@rishi-thak/nextcanvas
- Source: https://github.com/rishi-thak/nextcanvas