The stack: Vite or Next.js on Vercel, Supabase for database and auth, Resend for email, Cloudflare R2 for storage, and one analytics tool once you have users. The services are the same ones most agent-built apps converge on. What changes with Cursor is everything around them: the ignore file, the rules, and how you keep the repository legible to a tool that reads all of it.
Cursor is an editor. It indexes your project so it can answer questions with the relevant files in context, and it offers several modes: inline completion, a chat that can edit, and an agent mode that makes multi-file changes and runs commands. The practical consequence is that a messy repository makes Cursor worse in a way it does not make a terminal agent worse, because retrieval quality depends on your file organisation.
Why these services
Hosting with preview deployments. Cursor's agent mode produces multi-file changes quickly, and a preview URL per branch is how you find out whether one worked without merging it. Vercel and Netlify both do this well.
Supabase. Postgres plus auth and storage in one project, and generated TypeScript types from your schema. That last part matters more with Cursor than anywhere else: typed database calls mean the editor's completions are correct rather than plausible, and plausible is the failure mode you are trying to avoid.
Resend. Small, well documented API surface, so the generated code is right the first time. Verify your domain early.
Cloudflare R2. Storage with no egress charge. One less meter to reason about.
One analytics tool, later. Not two. Two is what happens when the agent picks and you do not.
Setup, in order
- Scaffold with the framework's own create command, then open the folder in Cursor and let the index build before asking it anything.
- Commit
.gitignoreand.cursorignorein the first commit, both containing your env files. - Write the rules file before the first feature.
- Deploy the empty app and confirm the preview URL works.
- Add the database, generate types from the schema, and build one feature end to end including sign-in.
- Add email, verify the domain, test from production.
Rules files
Cursor reads project rules from files in the repository. The location and format have changed across versions, from a single .cursorrules file to a directory of scoped rule files, so check the current documentation for where yours should live. The content is what matters:
Stack: Next.js App Router, TypeScript strict, Tailwind, Supabase.
- Database types are generated. Never hand-write a row type.
- All Supabase calls go through lib/db.ts.
- Components under 150 lines. Split rather than nesting conditionals.
- No new dependencies without asking.
- Never read, print or commit .env files.
- After any change, run: npm run typecheck
Keep it short. A rules file that runs to three pages is a rules file that gets partially ignored, and the rules that matter are the ones about where code lives and what must be verified.
.cursorignore and what the editor sees
An ignore file keeps paths out of the index and out of context. Put your env files there, along with anything large and generated that would dilute retrieval:
# .cursorignore
.env
.env.local
*.pem
secrets/
node_modules/
.next/
dist/
coverage/
*.sqlite
public/uploads/
Two reasons, not one. The security reason is obvious. The quality reason is that a repository full of build output and lock files retrieves worse, because the index is competing with noise. A lean index is a better assistant.
If you work on anything with confidentiality obligations, read the current documentation on what is sent to the model and what a privacy or local mode changes, and decide deliberately rather than by default.
Which mode for which job
Inline completion for typing you already know how to do. It is fastest and safest because you are reviewing character by character.
Chat with the file open for questions, explanations and single-file edits. Best when you want to understand before changing.
Agent mode for multi-file work: a rename across the codebase, wiring a new route through several layers, adding a table and everything that touches it. Give it a branch of its own, and read the whole diff before merging.
The failure mode is using agent mode for small things. A large autonomous change for a two line fix produces a diff you skim rather than read, and skimmed diffs are how inconsistent patterns spread through a codebase.
Keep the repository legible
Because retrieval is doing the work, the habits that help a human reader help the tool measurably. Descriptive file names beat clever ones. Files under a couple of hundred lines retrieve more precisely than one enormous module. Colocate related code so a feature is one directory rather than five. Delete dead code instead of commenting it out, because commented code still gets retrieved and still confuses the answer.
None of this is new advice. It just has a faster feedback loop now: a tidy repository visibly produces better completions within a day.
Generated types are the cheapest quality win
If your database can emit TypeScript types for its schema, generate them and commit them. This is the single highest leverage thing you can do for completion quality, because it converts the editor's guesses about your data into facts it can look up. A misspelled column becomes a red squiggle as you type instead of a runtime error in production, and the agent stops inventing fields that sound right.
Add the generation command to your package scripts and run it after every migration. Then put a line in the rules file saying row types are generated and must never be hand-written, because an agent that cannot find a type will cheerfully write one, and a hand-written duplicate that drifts from the schema is worse than no type at all.
Week two
Add error tracking and one analytics tool. Write a RUNBOOK.md that says which console answers which question. Turn on branch protection so agent-written changes go through a pull request, even if you are the only reviewer, because a diff you look at in a review interface gets read more carefully than one you skim in an editor. Leave payments, queues and anything with a compliance process until something forces the issue.
The env file
Same principle as any agent-assisted project. The values never go in the repository, the names do:
# .env.example
SQUIID_API_KEY=
NEXT_PUBLIC_SITE_URL=http://localhost:3000
With Squiid that single key reaches the database, email, storage and every other connected service through the gateway, and the provider credentials stay in Squiid's custody rather than in the file your editor indexes. If you prefer to hold provider keys directly, keep them out of the index with the ignore file and expect to rotate all of them the first time one leaks.