A free, open-source click-to-edit overlay for Next.js landing pages. Toggle Edit Mode, click any element, and change text, colors, sizing, spacing, links, and structure — live. Edits persist to localStorage by default (no backend required).
Optional AI editing: describe changes in natural language ("make the heading bigger and blue") and Claude applies them. Bring your own API key.
npx degit vikas8520-coder/edit-overlay my-site
cd my-site
npm install
npm run devOpen http://localhost:3000, click Edit Mode (bottom-right), and start editing.
Free out of the box (no backend, no API keys):
- Click-to-edit text, background color, text color, font size, padding
- Button links (href) that work as real
<a>tags in view mode - Move elements up/down, add child elements, delete elements
- Persists to localStorage automatically (debounced)
- Reset to default at any time
Optional AI editing (bring your own key):
- Natural language prompts: "rewrite this to be more punchy", "make it green"
- Powered by Claude Haiku via Anthropic API
- Only requires
ANTHROPIC_API_KEY— no key, no cost
Editing is dev-only. The edit button only appears when process.env.NODE_ENV === "development" — i.e., when you run npm run dev on localhost. In production builds, the button is hidden and editing is impossible. No auth, no login, no secrets to manage.
The workflow:
- Run
npm run devlocally - Click Edit Mode (bottom-right), edit your content
- Edits persist to localStorage automatically
- When you're happy, copy the saved tree JSON into your code (or commit the localStorage snapshot)
- Deploy — production shows the final content, no edit button
This keeps editing simple: it's a developer tool, not a multi-user CMS. You edit locally, commit, deploy. Done.
- Next.js (App Router) + TypeScript
- React 19
- Tailwind CSS v4
- No backend required (localStorage default)
- No auth required (dev-only editing)
- No UI library
npm install @anthropic-ai/sdkCreate .env.local:
ANTHROPIC_API_KEY=sk-ant-your-key-here
Restart the dev server. The AI edit box now appears in the side panel.
No key? The AI section shows a helpful error when you try to use it. Everything else works fine without it.
The persistence layer is a single file: src/lib/persistence.ts. It exports two functions:
export async function loadTree(): Promise<TreeNode | null>
export async function saveTree(tree: TreeNode): Promise<void>The default implementation uses localStorage. To persist to a database instead, replace the function bodies with fetch calls to your API. Examples for common backends:
Supabase:
export async function loadTree() {
const { data } = await supabase.from("content").select("tree").eq("key", "landing").single();
return data?.tree ?? null;
}
export async function saveTree(tree: TreeNode) {
await supabase.from("content").upsert({ key: "landing", tree });
}MongoDB (Mongoose):
export async function loadTree() {
const doc = await ContentModel.findOne({ key: "landing" });
return doc?.tree ?? null;
}
export async function saveTree(tree: TreeNode) {
await ContentModel.findOneAndUpdate(
{ key: "landing" }, { tree }, { upsert: true }
);
}Any REST API:
export async function loadTree() {
const res = await fetch("/api/content");
if (!res.ok) return null;
const data = await res.json();
return data.tree ?? null;
}
export async function saveTree(tree: TreeNode) {
await fetch("/api/content", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tree }),
});
}src/
app/
layout.tsx # root layout
page.tsx # client orchestrator: state, persistence, edit mode
globals.css # tailwind + edit-mode hover/selected styles
api/ai-edit/route.ts # optional AI endpoint (Claude)
lib/
tree.ts # types, default tree, helpers (find/update/clone)
treeOps.ts # structural ops (move/delete/insert/createNode)
persistence.ts # swappable persistence adapter (localStorage default)
components/
NodeRenderer.tsx # walks the tree, renders each node by type
EditOverlay.tsx # floating View/Edit toggle button
SidePanel.tsx # right-side property editor + AI prompt
The page is not hardcoded JSX — it renders from a JSON node tree (the content model). Each node has a type (heading, paragraph, button, card, etc.), props (text, colors, sizes), and optional children. The NodeRenderer walks this tree and maps each type to a React element.
When you edit, you're modifying the tree in React state. The tree auto-persists (localStorage or your custom backend). On reload, the saved tree is loaded and rendered.
This architecture is what makes every edit persistable and is the same pattern used by visual editors like Lovable, Base44, and Webflow.
| Type | Container? | Editable props |
|---|---|---|
hero |
yes | bgColor, padding |
section |
yes | bgColor, padding |
grid |
yes | bgColor, padding |
card |
yes | bgColor, padding |
footer |
yes | bgColor, padding |
heading |
no | text, textColor, fontSize |
paragraph |
no | text, textColor, fontSize |
button |
no | text, bgColor, textColor, fontSize, padding, href |
badge |
no | text, bgColor, textColor, fontSize |
MIT — free to use, modify, and distribute. See LICENSE.