The 12 Patterns I Keep Fixing

I use Cursor and Claude Code daily on a Next.js 15 + Tailwind v4 + Supabase stack. The tools are fast, but after enough real projects I noticed the same thing over and over: I kept correcting the exact same mistakes.

Unnecessary "use client", getSession() for auth decisions, synchronous params on Next.js 15, select('*') everywhere, Tailwind v3 config habits in v4 projects, browser Supabase clients on the server, missing Zod validation, outdated form APIs, no revalidatePath(), and missing loading.tsx / error.tsx boundaries.

After logging those corrections for weeks, the pattern became obvious. The same 12 mistakes explained most of the cleanup work. So the right fix was not more prompting. The right fix was a reusable rules layer.

  • "use client" everywhere instead of Server Components by default.
  • getSession() used for server auth decisions.
  • Synchronous params and searchParams in Next.js 15 routes.
  • select('*') instead of explicit columns.
  • tailwind.config.js patterns in Tailwind v4 projects.
  • Browser Supabase clients imported into server code.

Why AI Coding Tools Keep Getting This Wrong

The biggest issue is training-data lag. A large amount of the code these models learned from was written against Next.js 13 or 14, Tailwind v3, and older Supabase SSR patterns. Your stack moved. The default generated code often did not.

Next.js 15 made params async. Tailwind v4 moved design tokens into CSS with @theme. Supabase SSR wants a clean split between browser and server clients. React 19 pushed useActionState. Those are not edge details; they are workflow-shaping defaults.

If the model still thinks the old defaults are fine, you keep getting code that runs but drifts away from the stack you are actually shipping.

Next.js 15+

Async route params

Dynamic pages, layouts, and generateMetadata need Promise<params> and an explicit await.

Tailwind v4

CSS-first tokens

@theme and oklch replace the old JS config-first mental model.

Supabase SSR

Split clients by environment

Server and browser clients need different setup. One global client creates auth mistakes fast.

Next.js 15 param fix

// Broken on Next.js 15
export default function Page({
  params,
}: {
  params: { slug: string }
}) {}

// Correct
export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
}

The Fix: One File That Changes the Default Output

A .cursorrules or .mdc file is a persistent system layer. Instead of restating your stack conventions on every prompt, you encode them once and let the model inherit them before generation starts.

The important lesson is that vague rules do not work. 'Use best practices' gets ignored. 'Return { error } objects from Server Actions and never throw' changes the output immediately. The more concrete the rule, the more likely the model is to follow it.

Good rules are not motivational. They are executable defaults.

The models do not need more encouragement. They need sharper constraints, concrete examples, and version-aware defaults.

Before and After — Real Output

The biggest difference is not cosmetic. The same prompt produces a completely different architecture once the rules are installed.

Without rules

'use client'

import { useEffect, useState } from 'react'

export default function PostsPage() {
  const [posts, setPosts] = useState<any[]>([])

  useEffect(() => {
    supabase
      .from('posts')
      .select('*')
      .then(({ data }) => setPosts(data ?? []))
  }, [])
}

With rules

import { createServerClient }
  from '@/lib/supabase/server'

export default async function PostsPage() {
  const supabase = await createServerClient()
  const { data, error } = await supabase
    .from('posts')
    .select('id, title')
    .order('created_at', { ascending: false })

  if (error) return <Error />
}

Server Actions — The Other Big Gap

AI-generated Server Actions are another repeat failure zone: browser clients imported on the server, no validation, getSession() used as auth, inserts with no structured error return, and no cache revalidation after writes.

This is exactly the type of failure that disappears once you encode the action contract directly into the rules file.

Typical bad Server Action

'use server'
import { createClient } from '@/lib/supabase/client'

export async function createPost(formData: FormData) {
  const supabase = createClient()
  const { data: { session } } = await supabase.auth.getSession()

  await supabase.from('posts').insert({
    title: formData.get('title'),
    content: formData.get('content'),
    user_id: session?.user?.id,
  })
}

What I Learned About Writing Effective Rules

After iterating on the pack and testing it against real projects, a few principles kept proving themselves.

  • Show negative examples explicitly. 'Never use sync params from older Next.js patterns' is stronger than a general reminder.
  • Code beats prose. The models copy patterns faster than they interpret abstract guidance.
  • Version numbers matter. Next.js 15+ triggers different output than just Next.js.
  • Keep the file tight. Once the rules grow too long, the weakest instructions sink to the bottom of the context stack.

What’s in the Pack

The public repo proves the approach. The paid pack is the production version: deeper rule coverage, more stack-specific defaults, ready-to-paste prompts, and a fuller Claude Code / Codex reference layer.

Free GitHub snippet versus the full production pack.

LayerFree (GitHub)Full Pack ($19)
Core conventions20 rules400+ rules
Async params fixIncludedIncluded
Server vs Client ComponentsIncludedIncluded
Supabase client setupIncludedIncluded
Server Actions + ZodNot includedIncluded
React 19 useActionState formsNot includedIncluded
Tailwind v4 @theme + oklchNot includedIncluded
Auth middleware patternNot includedIncluded
RLS policy patternsNot includedIncluded
Error boundaries + loading statesNot includedIncluded
15 ready-to-paste promptsNot includedIncluded
Claude Code / Codex skill depthBasicFull
QA tested scenariosNot includedIncluded

Where It Fits in the Stack

The pack is built for Cursor first, but the conventions are portable. The same stack assumptions can be carried into Claude Code, Codex CLI, Windsurf, Continue.dev, and converted for Copilot-style instruction files.

And if you need to move between formats, the free converter on 0toprod.dev already exists for that workflow.

Next.js Pack

Stop fixing the same AI mistakes by hand.

The full pack gives you the tested rules, the prompt layer, and the stack-specific defaults for Next.js 15+, Tailwind v4, and Supabase. Start with the free GitHub snippet if you want to test the approach first.

Get the full pack — $19Free GitHub snippet