50% off SaaS Starter Kit — only for the first 100 buildersGrab it →
← Back to blog
vibecodingMay 19, 2026·8 min read

AI Pair Programming: Getting the Most Out of Cursor and Claude Code

We've been using Cursor and Claude Code daily for months. Here's what actually works, what wastes your time, and how to stop fighting the AI.

Robert Seghedi

Robert Seghedi

Co-founder, peal.dev

AI Pair Programming: Getting the Most Out of Cursor and Claude Code

We've both been writing code long enough to remember when autocomplete was just IntelliSense suggesting variable names. Now we have Claude explaining why our architecture is wrong at 11pm while we're trying to ship a feature. It's weird. It's also genuinely good, once you figure out how to use it without losing your mind.

After months of daily Cursor and Claude Code usage building peal.dev templates, we've developed opinions. Strong ones. This isn't a hype post — AI coding tools have real failure modes that nobody talks about. But they also genuinely make us faster, and the gap between developers who use them well and developers who don't is only growing.

Cursor vs Claude Code: They're Not the Same Tool

People treat these as interchangeable. They're not. Cursor is an IDE — it has your full codebase context, file navigation, inline diffs, and it integrates into your actual editing workflow. Claude Code is a CLI agent that can read files, run commands, write code, and iterate on its own. The mental model is different.

Cursor is best when you're in the flow and need a fast collaborator. You're looking at a function, you want to refactor it, you want a quick explanation, you want to generate a test. It's ambient assistance. Claude Code is better when you have a task that requires multiple steps, reading across many files, or running commands to verify things work. Think of it as delegation vs. collaboration.

We reach for Cursor when we're actively writing. We reach for Claude Code when we want to hand something off — 'implement this feature end-to-end, update the types, write the tests, make sure it compiles'. The latter works surprisingly well when scoped correctly.

The Prompt Engineering Nobody Talks About

The biggest thing that improved our AI output wasn't a specific prompt trick. It was treating every AI interaction like we'd treat a new team member's first task: give them context, constraints, and a definition of done. Vague instructions get vague code.

Here's a before/after that changed how we work. This kind of prompt gets you mediocre results:

// Bad prompt
"Add authentication to my Next.js app"

This one gets you something actually usable:

// Good prompt
"I'm using Next.js 14 with the App Router, Prisma with PostgreSQL, and
next-auth v5. I need to add a protected /dashboard route.

Constraints:
- Use the existing User model in schema.prisma (see @schema.prisma)
- Session should include the user's `plan` field
- Redirect unauthenticated users to /login, not /api/auth/signin
- Don't modify any existing auth config in @auth.ts

Only create or modify these files:
- app/dashboard/page.tsx
- app/dashboard/layout.tsx
- middleware.ts

Don't install new packages."

The difference is dramatic. Constraints are the secret ingredient. When you tell the AI what NOT to do, it stops generating plausible-looking code that doesn't fit your actual project.

Cursor's .cursorrules File Is Underused

If you're not using a .cursorrules file (or the newer .cursor/rules directory), you're leaving a huge amount of value on the table. This file tells Cursor about your project conventions, and it gets injected into every request automatically. You write it once, and every AI interaction becomes context-aware.

# .cursorrules

## Project Stack
- Next.js 14 App Router (not Pages Router)
- TypeScript strict mode
- Prisma ORM with PostgreSQL
- Tailwind CSS + shadcn/ui components
- next-auth v5 for authentication
- Stripe for payments

## Conventions
- Server Components by default, add 'use client' only when needed
- All database queries go in /lib/db/*.ts files, never inline in components
- Use zod for all form validation and API input parsing
- API routes return { data, error } shape — never throw, always return
- Prefer named exports over default exports
- All currency is stored in cents (integer), displayed with formatCurrency() from /lib/utils

## What NOT to do
- Don't use useEffect for data fetching — use Server Components or SWR
- Don't install new packages without asking first
- Don't modify /lib/auth.ts without being explicitly asked
- Don't use `any` type — use `unknown` and narrow it

## File naming
- Components: PascalCase (UserCard.tsx)
- Utilities: kebab-case (format-currency.ts)
- Routes: lowercase with dashes (app/user-settings/page.tsx)

We update this file whenever we establish a new convention. When we decided all API routes should return { data, error } instead of throwing, we added it to .cursorrules immediately. Now the AI generates that pattern automatically. It sounds trivial, but on a codebase with 50+ files, consistency enforcement via AI rules is genuinely valuable.

When to Stop Trusting the AI (Without Stopping the Collaboration)

This is where most people go wrong. They either trust the AI completely (bad), or they get burned once and go back to writing everything manually (also bad). The right approach is developing a sense of when to verify.

AI models are confidently wrong about a specific set of things. Once you know what they are, you can catch it fast:

  • Next.js App Router patterns — models still mix up App Router and Pages Router behavior, especially around cookies, headers, and redirect()
  • Package APIs that changed recently — Claude's training has a cutoff, and next-auth v5, for example, has a completely different API than v4
  • Your specific business logic — the AI doesn't know your domain, so it'll generate plausible-looking logic that's semantically wrong
  • Database queries involving complex joins or transactions — it'll generate valid Prisma, but often not the most efficient or correct query for your data model
  • Security-sensitive code — always review auth middleware, permission checks, and anything touching user data yourself

Stefan got burned badly on the next-auth v5 thing. He asked Claude Code to implement OAuth and it generated perfectly valid next-auth v4 code, but we were on v5. The API is different enough that nothing worked, and the error messages weren't obvious about why. We spent an hour debugging what a 30-second check of the docs would have caught. Now we explicitly state the version in every auth-related prompt.

Claude Code for Larger Tasks: A Practical Pattern

Claude Code's real power is agentic tasks — letting it read your codebase, write code, run commands, and iterate. But it can also go off the rails without guardrails. Here's the pattern we've landed on for delegating larger tasks:

# Claude Code task structure that actually works

## Task
Implement a basic usage-based billing system using Stripe Metered Billing.

## Relevant files to read first
- prisma/schema.prisma (existing data model)
- lib/stripe.ts (existing Stripe client setup)
- app/api/stripe/ (existing webhook handlers)
- types/index.ts (shared types)

## What needs to happen
1. Add `stripeSubscriptionItemId` to the User model in schema.prisma
2. Create a `recordUsage(userId: string, quantity: number)` function in lib/stripe.ts
3. Add a POST /api/usage endpoint that calls recordUsage (authenticated, validates input with zod)
4. Write a test in __tests__/usage.test.ts using the existing test setup

## Definition of done
- TypeScript compiles with no errors (run: npx tsc --noEmit)
- Existing tests still pass (run: npm test)
- New endpoint returns { data: { recorded: true } } on success

## Do NOT
- Modify any existing Stripe webhook handlers
- Change the User model beyond adding the one field
- Install any new packages

The 'definition of done' with actual commands is key. Claude Code will run those commands to verify its own work, which catches a lot of type errors and broken tests before you even look at the output. It's like having a junior dev who's obsessive about making the CI green.

Give the AI a definition of done it can verify programmatically. If it can run a command to check its own work, it will — and it'll iterate until it passes.

The Drift Problem (And How to Fix It)

Here's something nobody warned us about: codebases that are AI-assisted heavily can develop architectural drift. Each AI-generated piece is locally coherent but globally inconsistent. After a few weeks of heavy AI usage without discipline, you look at your codebase and realize some API routes return one shape, others return another. Some components fetch data in Server Components, some use SWR, some use useEffect. All technically fine. All subtly inconsistent.

This isn't an AI problem exactly — it's a code review problem that AI amplifies. You merge things faster, so drift accumulates faster. The fixes:

  • Keep your .cursorrules file updated with new decisions — it becomes your living architecture doc
  • Do a brief architecture review every few days, not just feature reviews
  • When you notice drift, fix it and add the convention to .cursorrules immediately
  • Use TypeScript strictly — let the type system catch inconsistency the AI introduces
  • Don't skip code review on AI-generated code because it 'looks fine'

We caught ourselves doing exactly this on a peal.dev template. Three different patterns for handling form state, all AI-generated, all working, but inconsistent enough that adding a new form was confusing. We spent half a day standardizing and documenting. The AI would have been consistent from the start if we'd been more specific in our early prompts.

What We Actually Use Daily

Here's our honest current workflow, not the aspirational one:

  • Cursor inline (Cmd+K) for: renaming/refactoring, writing boilerplate types, quick fixes, explaining code we didn't write
  • Cursor chat with codebase context for: 'why is this broken', writing tests for existing functions, generating Prisma queries we're not sure about
  • Claude Code for: implementing a full feature end-to-end, database migrations with data transforms, large refactors touching many files
  • Neither for: anything security-critical (we write that ourselves and then ask AI to review), business logic that's core to the product, anything where 'plausibly correct' isn't enough

The productivity gain is real but uneven. Simple tasks — write a test, generate a zod schema from a TypeScript type, refactor this function — are dramatically faster. Complex tasks that require understanding your domain deeply are still mostly human work, with AI doing the typing.

Most of the peal.dev templates were built with this workflow. The boilerplate — auth setup, Stripe webhook handlers, email templates — gets scaffolded fast with AI. The parts that make a template actually good (the edge cases, the UX decisions, the error states that matter) are still mostly us arguing over a screen at 1am.

AI is great at writing the code you could write yourself but don't want to. It's not great at knowing what code you should write. That part is still yours.

One last thing: the developers who get the most out of these tools aren't the ones who trust AI the most. They're the ones who know their own codebase well enough to instantly spot when the AI has gone sideways. Strong fundamentals make you a better AI user, not a worse one. Use the time you save on boilerplate to actually understand the code that matters.

Newsletter

Liked this post? There's more where it came from.

Dev guides, honest build stories, and the occasional 2am debugging confession — straight to your inbox. No spam, unsubscribe anytime.

Browse templates
Written by humansWeekly dropsSubscriber perks

Join the Discord

Ask questions, share builds, get help from founders