Build a demo with Cursor inside your repo
Build a demo with Cursor inside your repo, review each generated step, test the flow, and keep the demo owned by engineering instead of a separate tool.

On a previous project, I kept the demo in a separate Notion-linked Loom folder. It felt organized right up until I shipped a dashboard redesign in the middle of a sprint. The sidebar moved, two metric cards got renamed, and the demo showed the old layout for two more weeks before a beta user pointed it out on a call. Fixing it meant re-recording six screens, updating three timestamps in the Notion doc, and losing most of a Friday afternoon I had not planned for.
The problem was not the recording tool. It was the fact that the demo lived somewhere the product could not reach.
If you build a demo with Cursor inside your repo, the product and the demo share the same codebase. Cursor can keep them in sync the same way it keeps your components in sync.
Put the demo in the same repo as the product
The folder layout that keeps the demo close to the app
A repo-native demo does not need its own project. It needs a folder:
The `/demo` directory sits at the root alongside `/src`. It imports shared components directly from `../src/components` — the same `Button`, `Card`, and `NavBar` your product uses. `demo/data/seed.ts` holds the fake but realistic data the demo renders, so it never touches production state.
In practice, this means one simple thing: when a component changes in `/src`, the demo picks it up. No re-export step. No copy-paste drift.
Why ownership changes what Cursor can safely edit
Cursor can only keep the demo aligned when it can see the whole picture: routes, shared logic, component props. When the demo is a recording in someone else's SaaS, Cursor has nothing useful to work with. When the demo is `demo/screens/dashboard.tsx` in your repo, Cursor can read the current `NavBar` props, check the route definitions in your router config, and update the demo in the same edit session.
Owning a recording means owning a snapshot. Owning code means owning something that can keep changing. Vercel's MCP integration with Cursor shows the pattern at scale: agent tooling works better when it has direct access to the project graph, not a screenshot of it.
Use a phased Cursor prompt sequence instead of one giant ask
One prompt that says "build me a complete polished interactive demo of my SaaS dashboard" will usually produce something that compiles and still feels wrong. A phased approach gives you a working skeleton you can review before Cursor adds detail.
Prompt 1: scaffold the smallest useful path
This prompt is narrow on purpose. Cursor builds a demo you can read in five minutes. The skeleton either works or it doesn't, and you know that before you go any further.
Prompt 2: add the product details after the skeleton works
Once the skeleton renders and the navigation advances:
Cursor now has a working scaffold to edit against. Specific edits on a skeleton are safer than a full build from scratch. The scope is bounded, and the diff is readable.
Prompt 3: ask for the ugly edge cases before shipping
This prompt surfaces the bugs that make demos feel broken in front of a real viewer. Route mismatches, stale prop names, and state that does not reset are the three failures I see most often in Cursor-generated demo code. Catching them before merge is cheaper than catching them on a call. Iterative agent review loops follow the same idea: each pass is narrower and safer than one large generation.
Review Cursor-generated changes like you would a PR
What to check in the diff before you merge anything
A Cursor-built demo touches real shared components. Review the diff with the same attention you would give a junior engineer's first PR:
- Route strings — does `demo/screens/dashboard.tsx` reference `/dashboard` or `/app/dashboard`? Check against `src/router.tsx`.
- Shared component imports — did Cursor import a component that was recently renamed or removed? A stale import compiles until it does not.
- Copy drift — does the demo headline match the current marketing copy, or did Cursor invent something plausible sounding?
- Accidental duplication — did Cursor copy logic from `src/` into `demo/` instead of importing it? That's a fork waiting to happen.
The human review pass that catches fake correctness
Cursor writes code that looks right. The failure mode is not syntax errors. It is code that passes lint, renders without crashing, and still shows the wrong product.
A Cursor-built demo once gave me a dashboard with a "Revenue" card that pulled from `seed.ts` correctly but used a currency format that did not match the product's locale settings. The diff looked clean. The rendered output looked wrong to anyone who had seen the real product.
Run the demo in a browser before you call the diff reviewed. The diff tells you what changed. The browser tells you what the viewer will actually see.
Test the demo in a browser before you call it done
The route, the form, and the state reset
Three checks cover most AI-built demo workflow failures:
- Route check — navigate directly to `/demo` in your browser. Does it load, or does the router fall through to a 404? If the demo route is not registered in `src/router.tsx`, it only works in local dev with a direct file import.
- Form state — if the demo includes any input field, type something, advance to the next screen, then restart the demo. Does the field clear? State that persists across demo restarts looks like a bug to every viewer after the first.
- State reset — click through the full flow, hit the final screen, then click "Start over" or refresh. Does the demo return to screen one cleanly, or does it land in a half-initialized state?
The bug you only catch when you click through end to end
On one validation pass, the onboarding checklist screen rendered fine in isolation. End to end, it only appeared after the dashboard screen, and the dashboard screen had a "Continue" button that advanced state by two steps instead of one, skipping the checklist entirely. The bug was in the state machine Cursor generated: `setCurrentScreen(prev => prev + 2)` instead of `prev + 1`. It did not show in the diff. It showed in the second click.
Fix it with Cursor: paste the state machine code and describe the skip. One targeted prompt, one line fix.
Debug the routing, form state, and versioning mistakes Cursor will make
When the demo route and product route drift apart
Cursor often generates demo navigation using string literals copied from context. But if your router uses dynamic segments (`/app/:orgId/dashboard`), the demo hardcodes `/app/demo-org/dashboard` and breaks the moment the route shape changes. The fix is to import route constants from a shared `src/routes.ts` file rather than hardcoding strings in the demo. If that file does not exist yet, create it as part of the demo build.
When form state resets at the wrong time
The common form-state bug: Cursor initializes form state inside the component that renders the demo screen, not at the top-level demo state machine. When the demo navigates away and back, the component unmounts and remounts, which resets the form mid-flow. Move form state up to the demo's root component so it persists across screen transitions and only resets on an explicit restart action.
When versioning turns the demo into a stale fork
If Cursor copies a component's logic into `demo/` instead of importing from `src/`, you now have two copies of that logic. The next time the product component changes, the demo copy does not. A code-owned demo avoids this by importing, never copying. When you spot a copied block in the diff, replace it with an import before merging. That is the versioning fix, and it takes thirty seconds.
Hand the demo off so product and marketing can keep using it
Who owns the repo path after launch
Engineering owns the `/demo` directory and the shared components it imports. Product and marketing own the content inside `demo/data/seed.ts` and the copy strings in the screen components. That split means a marketer can open `dashboard.tsx`, find the `<h1>` tag, change the headline, and open a PR without touching a component, a route, or a state machine.
The change request that should be a one-line diff
"Can we update the CTA on the onboarding screen to say 'Set up your first project' instead of 'Get started'?" That is a one-line change in `demo/screens/onboarding.tsx`. With a repo-native demo, the person making the request can make the change themselves, or Cursor can make it from a one-sentence prompt. Neither path requires rebuilding the demo or touching the product code.
Update the demo when the product ships new UI
The next deploy should be a code update, not a rebuild
When the product ships a UI change, the update path is simple: open Cursor, describe what changed, let it update `demo/screens/` to match. The demo stays in the repo. Nobody re-records anything. If the `NavBar` component updated its props interface, Cursor finds every place `demo/` uses the old interface and fixes it in the same pass it would use to fix `src/`.
What breaks first when the product changes shape
Labels and route strings break first. They are the most likely to be hardcoded rather than imported. After that, component prop names. Layout changes are usually inherited automatically because the demo imports the real components. When a product ships a new navigation structure, audit `demo/data/seed.ts` and the hardcoded strings in screen components first. That is where the drift lives.
Where Inkly comes in
The problem this tutorial solves is structural: a demo that lives outside your repo cannot be maintained by the same tools that maintain your product. Cursor cannot re-prompt a Loom recording. It cannot update a Supademo capture. It can only edit code it can read.
Inkly is built on that premise. Every demo Inkly produces is code you own — HTML you can move into your repo, put next to your product, and hand to Cursor for the next update. The three-prompt loop (create, update, produce variants) maps directly onto the workflow this article describes: scaffold with a prompt, tighten with a second, fix edge cases with a third. When your product ships new UI, you re-prompt the demo code rather than re-recording screens.
The tradeoff is plain: Inkly's MVP requires you to bring your own agent — Cursor, Claude, or Codex. There is no hosted in-app agent yet. If you are already running Cursor on your codebase, that is not much of a tradeoff. If you are not, the repo-native workflow this article describes is the setup step. Either way, the demo becomes code your agent can maintain, not an artifact stuck in someone else's SaaS.
FAQ
Q: How do I build a demo with Cursor inside my existing repo instead of creating a separate demo workflow?
Create a `/demo` directory at the repo root that imports shared components from `/src` rather than duplicating them. Use a phased Cursor prompt sequence — scaffold first, add product details second, fix edge cases third — so each pass is narrow enough to review before you move on. The demo lives in version control alongside the product, so every product change is visible to Cursor when it updates the demo.
Q: What prompt sequence should I use so Cursor builds the demo in phases and I can review each step?
Three prompts: the first asks for the minimum navigable flow using existing shared components and a seed data file; the second tightens copy, labels, and UI details against the current product state; the third asks Cursor to check for route mismatches, stale prop names, and form state that does not reset. Review the diff and run the demo in a browser between each prompt. Do not chain them without a human check in between.
Q: How do I keep the demo owned by engineering so product and marketing can revise it later?
Split ownership by file, not by role. Engineering owns the component imports, route wiring, and state machine in the demo's root. Product and marketing own the copy strings in screen components and the seed data in `demo/data/seed.ts`. That split lets non-engineers open a PR to change a headline or a CTA without touching the product codebase.
Q: How do I validate that Cursor's output actually works before I call the demo finished?
Three browser checks: navigate directly to the demo route to confirm it is registered in the router; type into any form field, advance screens, restart, and confirm the field clears; click through the full flow end to end to catch state machine bugs that only appear in sequence. Cursor-generated code that passes lint can still skip screens or reset state at the wrong moment. Only a full click-through catches that class of bug.
Q: How do I update the demo when the product changes without re-recording everything?
Open Cursor, describe the product change (new nav label, renamed route, updated component prop), and let it update the demo screens in place. Because the demo imports from `/src` rather than copying logic, layout changes propagate automatically. What needs explicit updating is hardcoded strings and route literals. Those are the first places to audit after any product UI change.
Conclusion
If the demo lives in your repo, Cursor can maintain it the same way it maintains the product: by reading the code, not guessing from a screenshot. The update path becomes a prompt, not a re-record. The handoff becomes a PR, not a Loom link.
Take one existing demo flow this week, move it into a `/demo` directory next to your product code, and rebuild the first pass with Cursor using the three-prompt sequence above. That is the whole workflow, and once it is in the repo, it stays current.
Ship your next demo before the meeting starts
Interactive demos built from your real product and kept current as you ship, done for you.





