5 Mistakes You're Making When Vibe Coding Next.js in Cursor AI
If you're building Next.js apps in Cursor AI by just vibing with the AI and letting it generate code, you're not alone. But you might be making these critical mistakes that slow you down and create technical debt.
1. Not Setting Up Your .cursorrules File
The Mistake: You're letting Cursor AI guess your Next.js setup every single time. It defaults to Pages Router when you want App Router, suggests JavaScript when you prefer TypeScript, or creates files in the wrong directory structure.
Why It Matters: Without a .cursorrules file, you'll spend half your time correcting basic architectural decisions. The AI doesn't know you're using Next.js 14 with App Router, TypeScript, Tailwind, and shadcn/ui until you tell it—repeatedly.
The Fix: Create a .cursorrules file in your project root:
This is a Next.js 14 project using:
- App Router (not Pages Router)
- TypeScript
- Tailwind CSS
- Server Components by default
- Client Components only when needed (use 'use client' directive)
- shadcn/ui for components
File structure:
- app/ for routes
- components/ for reusable components
- lib/ for utilities
- actions/ for server actions
Always use:
- Async server components when possible
- Server actions for mutations
- TypeScript interfaces for props
- Descriptive component names
Now Cursor remembers your preferences across all chats and code generations.
2. Mixing Client and Server Components Without Understanding the Boundary
The Mistake: You ask Cursor to "add a button with onClick" and it slaps 'use client' at the top of your entire page component, turning everything into a client component—including your database queries and server-side logic.
Why It Matters: You lose all the benefits of Server Components: faster initial page loads, smaller JavaScript bundles, and direct database access. Your app becomes bloated and slow because everything now runs on the client.
The Fix: Be explicit in your prompts:
❌ Bad prompt: "Add a button to submit this form"
✅ Good prompt: "Add a submit button to this form. Keep the page as a Server Component and create a separate Button client component in components/ui/submit-button.tsx"
Better yet, use server actions:
// app/page.tsx (Server Component)
import { submitForm } from '@/actions/submit-form'
import SubmitButton from '@/components/ui/submit-button'
export default async function Page() {
return (
<form action={submitForm}>
{/* form fields */}
<SubmitButton />
</form>
)
}
Learn the rule: Push client components as far down the component tree as possible.
3. Not Specifying Data Fetching Patterns
The Mistake: You vibe code a "products page" and Cursor generates a useEffect with fetch() in a client component, when you should be using server-side data fetching.
Why It Matters: You're creating unnecessary loading states, handling errors client-side, and making your users wait longer to see content. Next.js App Router gives you powerful server-side patterns, but Cursor defaults to old React patterns if you don't guide it.
The Fix: Be specific about data fetching in your prompts:
❌ "Create a page that shows products from the API"
✅ "Create a server component page that fetches products directly from the database using Prisma. Add proper TypeScript types and error handling."
Or if you need dynamic data: ✅ "Create a server component that fetches products with { cache: 'no-store' } for real-time data"
Example prompt:
Create app/products/page.tsx as an async server component that:
1. Fetches products from our Supabase database
2. Uses proper TypeScript types
3. Handles errors with error.tsx boundary
4. Shows a loading state with loading.tsx
5. Renders a responsive grid of product cards
4. Generating Entire Files When You Only Need Small Changes
The Mistake: You ask Cursor to "add a loading spinner" and it regenerates your entire 200-line component, potentially breaking working code or losing your recent changes.
Why It Matters: When Cursor rewrites large files, it can introduce subtle bugs, remove comments, change formatting, or undo uncommitted changes. You end up spending time reviewing and fixing code that was already working.
The Fix: Use surgical prompts with Cmd+K (inline editing):
Instead of asking in chat: ❌ "Update UserProfile.tsx to add a loading state"
Highlight the specific section and use Cmd+K: ✅ "Add a loading spinner here using lucide-react's Loader2 icon"
For larger changes, be explicit: ✅ "In the UserProfile component, only modify the fetchUser function to add try-catch error handling. Don't change anything else."
Pro tip: Commit your working code before asking Cursor for changes. Git is your safety net.
5. Not Providing Context About Your Database Schema
The Mistake: You prompt "create a page to display user posts" and Cursor generates code with made-up field names like post.content when your database actually uses post.body, or assumes you're using Prisma when you're using raw SQL with Supabase.
Why It Matters: You waste time correcting field names, relationship structures, and database access patterns. The generated code looks right but breaks at runtime because it doesn't match your actual data model.
The Fix: Keep your schema documentation accessible to Cursor.
Option 1 - Add to .cursorrules:
Database: Supabase (PostgreSQL)
User table:
- id: uuid
- email: string
- created_at: timestamp
Post table:
- id: uuid
- user_id: uuid (foreign key to users)
- body: text (not 'content')
- published: boolean
- created_at: timestamp
Use Supabase client: import { createClient } from '@/lib/supabase/server'
Option 2 - Include schema in prompts:
Create a posts page that displays all published posts. Our Post type has:
- id, user_id, body, published, created_at
Use the Supabase client to query:
const posts = await supabase.from('posts').select('*, user:users(email)')
Option 3 - Keep a schema.md file and reference it:
@schema.md Create a page to display user posts with author information
The Vibe Coding Mindset: Guide, Don't Dictate
Vibe coding in Cursor AI is powerful when you provide just enough context and constraints. Think of it like pair programming with a very fast but occasionally forgetful colleague:
- Set up your environment once (
.cursorrules) - Be specific about architecture (Server vs Client Components)
- Specify data patterns (where and how to fetch)
- Make surgical edits (highlight and Cmd+K)
- Document your schema (schema.md or
.cursorrules)
The goal isn't to write perfect prompts—it's to provide enough context that Cursor generates code that fits your project without requiring major revisions.
Master these patterns, and you'll ship Next.js apps faster while maintaining clean architecture. The vibe? Intentional but flexible.
What's your biggest Cursor AI mistake? I'm building Loopl Team to help vibe coders visualize and manage their databases without fighting with SQL or schema complexity. If you're tired of database headaches interrupting your flow, check it out.
