DEV Community

Ned C
Ned C

Posted on

5 .cursorrules That Actually Changed What Cursor Generates (React/Next.js)

Most .cursorrules don't actually change anything. I tested 30+ rules over a couple weeks — same prompt, with and without each rule, comparing the diff. "Use TypeScript." "Prefer functional components." The AI was gonna do that anyway. Only 5 rules changed the output every time, and they all target the same blind spot.

If you're new to .cursorrules, it's a file in your project root that tells Cursor how to generate code. In theory.

The test

For each rule, I ran the same workflow:

  1. Prompt Cursor without the rule → save output
  2. Add the rule to .cursorrules
  3. Run the same prompt → save output
  4. Diff the two

If the output didn't change, the rule got cut. These 5 survived.

1. error.tsx alongside every page

Always create error.tsx alongside every page.tsx. error.tsx must 
be a client component that receives error and reset props. 
Display fallback UI with a retry button.
Enter fullscreen mode Exit fullscreen mode

I had "use error boundaries" at first and it did literally nothing. Cursor just ignored it. But when I changed it to specifically say "alongside every page.tsx" it started actually generating the file whenever I scaffold a route.

Without the rule: "Create a user profile page" → just page.tsx, nothing else.

With the rule: page.tsx + error.tsx with error/reset props and a retry button.

See the generated error.tsx

'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (

      <h2>Something went wrong</h2>
      <p>{error.message}</p>
       reset()} className="px-4 py-2 rounded-md bg-primary text-primary-foreground"&gt;
        Try again


  )
}
Enter fullscreen mode Exit fullscreen mode

Without the rule, this file simply doesn't exist. Cursor never thinks to create it.

The takeaway: vague instructions get ignored. Specificity is what makes rules work.

2. revalidatePath after mutations

After any create, update, or delete in a Server Action, call 
revalidatePath() for the affected route. Never rely on the 
client to refetch.
Enter fullscreen mode Exit fullscreen mode

This one cost me an embarrassing amount of time. I had a Server Action that was working fine — data saves, no errors, everything looks good. But the list just wouldn't update. I kept refreshing manually like an idiot until I realized Cursor never added revalidatePath().

It tells Next.js to re-fetch data for a route after a mutation. Without it, the page shows stale data until a manual refresh. Easy to forget as a human too — so not surprised the AI misses it every time.

Before/after diff

  'use server'

  export async function createPost(formData: FormData) {
    const title = formData.get('title') as string
    const body = formData.get('body') as string

    await db.post.create({ data: { title, body } })

+   revalidatePath('/posts')
  }
Enter fullscreen mode Exit fullscreen mode

One line. That's it. But Cursor never adds it without the rule.

3. loading.tsx with async pages

Colocate a loading.tsx with every page.tsx that does async data 
fetching. Skeleton UI matching the page layout, not a spinner.
Enter fullscreen mode Exit fullscreen mode

Same companion file pattern as error.tsx — Cursor makes the page but doesn't think about what happens while it loads. You get a white flash on navigation, then have to go back and add loading.tsx yourself.

The "skeleton UI matching the page layout" part matters. Without that specificity you get a centered spinner, which is better than nothing but not by much. Skeleton screens are what you actually want — they reduce perceived load time because the layout doesn't jump around.

4. Parallel routes for modals

Use parallel routes (@modal) with intercepting routes (..) for 
modal patterns. The modal should have its own page.tsx that 
renders the full content as a fallback when accessed directly.
Enter fullscreen mode Exit fullscreen mode

Ask Cursor for a modal flow — clicking a photo in a grid to open it bigger, say — and it goes straight to useState + some Dialog component. Which works, but then the URL doesn't change, you can't share a link to the modal, and the back button is weird.

The parallel routes approach is what Next.js actually wants you to do. Shareable URLs, back button works, and if someone opens the link directly they get a full page instead of a blank screen. Kind of a no-brainer once you see it — but Cursor will never reach for it on its own.

File structure Cursor generates with the rule

app/
├── @modal/
│   └── (.)photos/
│       └── [id]/
│           └── page.tsx    ← modal version
├── photos/
│   └── [id]/
│       └── page.tsx        ← full page fallback
├── layout.tsx              ← renders {modal} slot
└── page.tsx                ← the grid
Enter fullscreen mode Exit fullscreen mode

Without the rule, you just get page.tsx with an inline useState modal. No URL changes, no shareable links, no fallback.

5. not-found.tsx at every layout level

Create not-found.tsx alongside layout.tsx at each route segment. 
The not-found page should include navigation back to the parent 
segment, not just the home page.
Enter fullscreen mode Exit fullscreen mode

Here's the user experience without this rule: someone hits /dashboard/settings/xyz (bad URL) and lands on your root 404 — "Page not found. Go home." They were in the dashboard. They wanted the dashboard. Now they're on the homepage wondering what happened.

With the rule, Cursor creates not-found.tsx at each route segment. Hit a bad settings URL? You see "Setting not found" with a link back to /dashboard. Context-aware, not a dead end.

The pattern

All 5 target the same thing: companion files.

Cursor builds what you ask for but doesn't create the supporting files around it — error boundaries, loading states, not-found pages, modal route structures. It generates the happy path and stops.

If you're writing your own .cursorrules, focus on that. Don't waste rules on stuff the AI already does. Focus on the files it should create but doesn't.

Quick test for any rule: prompt Cursor with and without it. If the output doesn't change, delete the rule. You're burning context window for nothing.

What's next

These 5 had the most dramatic diffs, but they're not the only ones that work. The full set also covers caching strategies (revalidateTag patterns), automatic metadata generation, middleware auth guards, and a few more companion file rules I didn't have room for here.

I'm also putting together similar tested sets for Python and debugging workflows — same methodology, only rules that change the output survive.

If you want the complete React & Next.js collection (18 rules, all tested with before/after diffs), I published them here.


What rules have you found that actually change Cursor's output? I'd genuinely love to compare notes — drop them in the comments.

Top comments (2)

Collapse
 
mahima_heydev profile image
Mahima Arora

The revalidatePath one hit home. We had the exact same issue on a client project where mutations were "working" but the UI was stale, and everyone blamed caching or state management before realizing the server action just never told Next.js to refetch. Your point about specificity is spot on too. I've noticed the same pattern with CLAUDE.md files — vague instructions like "follow best practices" get completely ignored, but "create X file alongside Y with these exact props" actually changes behavior. The AI needs concrete, structural instructions, not vibes.

Collapse
 
nedcodes profile image
Ned C

The stale UI from missing revalidatePath is such a common trap. It looks like a caching bug, feels like a state management bug, but it's just a one-liner that's missing. I've seen teams spend hours debugging that before checking the server action.

The CLAUDE.md parallel is interesting too. I think it comes down to the same principle: LLMs are pattern matchers, not mind readers. "Follow best practices" has no pattern to match against. "Create a test file next to every component with these exact imports" gives it a template to follow.

The structural instruction framing is a good way to think about it. Rules that describe file structures, naming conventions, or import patterns tend to stick because they're verifiable. The model can check its own output against them.