Tinker AI
Read reviews
intermediate 4 min read

Cursor with React Server Components: prompts that produce correct boundaries

Published 2026-03-27 by Owner

React Server Components have been in production for years now, but Cursor’s defaults still don’t always know which side of the boundary your component should be on. The model writes 'use client' reflexively, hoists state into client components that didn’t need to, or imports server-only utilities into client components and breaks the build.

The root cause: most React tutorials in the model’s training data are pre-RSC. Without explicit guidance, the model defaults to “client component with hooks” because that’s the dominant pattern in its training.

Two changes fix this.

Step 1: a CLAUDE.md / .cursorrules section about RSC

Add to your .cursorrules or .cursor/rules/frontend.mdc:

This is a Next.js 15 App Router project. Components default to React Server
Components.

Rules:

1. Add 'use client' ONLY when the component needs:
   - useState, useEffect, useReducer, or other hooks that require client
   - Browser-only APIs (window, document, localStorage)
   - Event handlers (onClick, onChange, etc.)
   - Third-party libraries that require client (most chart libraries, some
     rich text editors)

2. If a component just renders data, it's a Server Component. Do not add
   'use client' to it.

3. Server Components can import Client Components (and pass props to them).
   Client Components cannot import Server Components, but they can render
   children passed in as props.

4. Data fetching belongs in Server Components by default. Use the fetch API
   directly with revalidate options. Do not use SWR, react-query, or similar
   client data fetching libraries unless the data must be fetched client-side
   (e.g., requires user interaction).

5. Server actions handle mutations. Define them in 'use server' files or
   inline functions. Do not write API routes for things that could be
   server actions.

6. When in doubt, start as a Server Component and only add 'use client' if
   the build or runtime tells you it's needed.

This is verbose but each rule prevents a specific failure I’ve seen the model make.

Step 2: “explain why” in your prompts

When asking for a new component, add: “explain why you chose Server Component or Client Component.”

The forced explanation prevents the model from defaulting to client component without reason. About half the time, the explanation reveals that the model was about to default unnecessarily — and the explanation step makes the model self-correct.

Example prompt that works:

Add a <UserProfile> component at app/users/[username]/page.tsx that shows the user’s name, bio, and post count. Data comes from the existing getUserByUsername function in lib/users/queries.ts.

Explain why this should be a Server Component or Client Component before writing the code.

Cursor responds:

This should be a Server Component because:

  1. It just fetches and renders data
  2. No interactivity (no buttons, forms, or state)
  3. The data comes from a server-side query function
  4. It can render at request time and stream HTML to the client

[proceeds to write the Server Component correctly]

Without the “explain why” step, the model would have written it as a Client Component with useEffect to fetch the data — wrong on multiple axes.

Common boundary mistakes the model makes

A non-exhaustive list of mistakes I see Cursor make on RSC code:

Wrapping a whole page in ‘use client’. When the page has one interactive element, the model often makes the whole page a client component. The fix is to make the page a server component and put 'use client' only on the interactive child. The rules file plus the explanation step catches most of these.

Using useState for data that comes from a query. The model writes const [user, setUser] = useState(null) and then tries to fetch in useEffect. The pattern is wrong for RSC. The Server Component should fetch directly. Caught by rule 4 above.

Importing server utilities into client components. Cursor occasionally imports a database client into a client component. Next.js complains at build time. The model then “fixes” by adding 'use client' to the database client file, which is worse. Rule 3 above is meant to catch this; explicit constraint helps.

Using useEffect for code that should run on the server. Components that read environment variables or check feature flags often get written with useEffect. The right answer is to do that work in the server component before rendering. Rule 5 mostly covers it; the explanation step helps too.

Verification

After Cursor produces code, a quick check:

# Look for 'use client' directives — there should be few
grep -r "'use client'" app/ components/ | wc -l

# Should match the number of interactive components, not total components

If your 'use client' count is creeping up to match total component count, the model has been defaulting wrong. Re-check the rules file is being loaded; remind the model in chat.

A more rigorous check: build the production bundle and look at the client bundle size. If your RSC project has a 500KB+ client bundle, you’re shipping too much. The fix is usually pulling Server Component logic out of the client bundle.

What still goes wrong

Even with the rules, two failure modes persist:

Mixing client/server code in shared utilities. The model occasionally writes a util file that uses process.env (server-only) but is imported by client components. The fix is to split server-only utils into a server-only package; client utils into client-only. Documenting this split in the rules helps but doesn’t always prevent it.

Streaming and Suspense. RSC’s streaming model with Suspense boundaries is hard to get right. The model can produce a working version but rarely produces an optimal version (right boundaries, right loading.tsx files). For perf-sensitive routes, manual review and iteration is needed.

These are the real RSC challenges, not solved by prompt engineering. The rules above get you to “correct first attempt” for routine work; you handle the harder stuff yourself.

Worth the setup

The 200 lines of rules above take 10 minutes to write. The “explain why” prompt habit takes a few sessions to internalize. The combination produces correct RSC code most of the time.

Without these, Cursor on RSC projects produces code that compiles but is structurally wrong — too much client, too many useEffect data fetches, wrong boundary placement. The compiled output is fine; the design is wrong; you’ve added work for the next person.

Get the boundaries right at write time. It compounds across the project.