Use Cursor to build predictable, scalable Next.js applications by validating server and client boundaries, preventing hydration mismatches, eliminating fetch waterfalls, and structuring UI around clear, testable rendering logic.
1. Server components must remain pure and deterministic.
2. Client components are required when using state, effects, refs, or browser APIs.
3. Hydration must match SSR exactly.
4. Data should be fetched at the highest boundary possible.
5. Every rerender should have an explainable source.
Ensure consistent behavior across server rendering, RSC flows, and client hydration by using Cursor to surface mismatch errors, waterfall queries, rerender loops, and browser-only code leaks.
1️⃣ Validate Server vs Client Component Boundaries
2️⃣ Ensure SSR and Hydration Output Match
Cursor can diff SSR HTML with hydrated DOM and show mismatches.
function Timestamp() {
return <div>{Date.now()}</div>;
}
Server returns a timestamp; client returns a new timestamp → mismatch.
function Timestamp({ initialTime }) {
return <div>{initialTime}</div>;
}
export async function getServerSideProps() {
return { props: { initialTime: Date.now() } };
}
Cursor can highlight exactly where DOM differs.
3️⃣ Remove Fetch Waterfalls
Nested fetches cause slow sequential loading.
function ProductsPage() {
return <List />;
}
function List() {
const [items, setItems] = useState([]);
useEffect(() => {
fetch('/api/items').then(r => r.json()).then(setItems);
}, []);
}
async function ProductsPage() {
const items = await getItems();
return <List items={items} />;
}
Now data loads once, server-friendly and cacheable.
4️⃣ Use Pure Rendering Helpers
Pure functions isolate UI logic and reduce rerenders.
Example:
function renderProducts(list) {
return list.map(item => <li key={item.id}>{item.name}</li>);
}
Cursor can extract this pattern automatically.
5️⃣ Trace Rerender Causes
Cursor can show rerender events and their exact triggers (prop changes, parent updates, context updates, unstable callbacks).
"use client"
function Dashboard() {
const [data, setData] = useState([]);
useEffect(() => {
fetch('/api/stats').then(r => r.json()).then(setData);
}, []);
const width = window.innerWidth;
return <UI data={data} width={width} />;
}
Issues:
async function Dashboard() {
const data = await getStats();
return <DashboardClient data={data} />;
}
"use client"
function DashboardClient({ data }) {
const width = typeof window !== 'undefined' ? window.innerWidth : null;
return <UI data={data} width={width} />;
}
Now SSR improves performance and hydration is stable.
safeClient(fn) – wraps browser-only logic
wrapSSRData(loader) – ensures server-side fetching
useHydrationLog() – surfaces SSR vs hydration mismatches
1. Capture SSR HTML
2. Hydrate and diff DOM
3. Navigate routes and log fetch calls
4. Trace rerender causes
5. Detect waterfalls and overfetching patterns
"Diff SSR HTML with hydrated DOM and show mismatches."
"Trace rerender sources for this component during navigation."
"Split this into server and client components while retaining behavior."
"Identify all fetch calls triggered and highlight waterfall chains."
Supports both App Router and Pages Router. Cursor is ideal for surfacing hydration issues, rerender sources, and data-fetching bottlenecks.