Skip to content

Commit dc27980

Browse files
committed
Add 2 more rules (22 total): hydration-safety, caching-revalidation
1 parent d044a3f commit dc27980

2 files changed

Lines changed: 130 additions & 0 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
description: Next.js caching and revalidation best practices
3+
globs: ["**/app/**", "**/lib/**"]
4+
alwaysApply: false
5+
---
6+
7+
# Caching & Revalidation
8+
9+
## Default Behavior
10+
Next.js 15 has specific caching defaults. AI models often get these wrong.
11+
12+
## Rules
13+
14+
### Server Components - Data Fetching
15+
For dynamic data that changes frequently:
16+
```typescript
17+
// Force fresh data on every request
18+
const data = await fetch(url, { cache: 'no-store' })
19+
20+
// Or revalidate every 60 seconds:
21+
const data = await fetch(url, { next: { revalidate: 60 } })
22+
```
23+
24+
### Route Segment Config
25+
For pages that must always show fresh data:
26+
```typescript
27+
// At the top of page.tsx or layout.tsx:
28+
export const dynamic = 'force-dynamic' // Never cache this page
29+
export const revalidate = 0 // Same as above
30+
```
31+
32+
For static pages that rarely change:
33+
```typescript
34+
export const revalidate = 3600 // Revalidate every hour
35+
```
36+
37+
### Server Actions — Always Revalidate
38+
After mutations, ALWAYS revalidate affected paths:
39+
```typescript
40+
'use server'
41+
import { revalidatePath } from 'next/cache'
42+
43+
export async function createPost(formData: FormData) {
44+
// ... create post in database
45+
revalidatePath('/posts') // ← NEVER forget this
46+
revalidatePath('/dashboard') // ← Revalidate all affected pages
47+
}
48+
```
49+
50+
### Anti-Patterns
51+
- NEVER assume `fetch()` will always return fresh data — check your cache settings
52+
- NEVER forget `revalidatePath()` after a mutation in a Server Action
53+
- NEVER use `cache: 'force-cache'` for user-specific data
54+
- NEVER rely on client-side state to show "updated" data after a mutation — revalidate the server cache
55+
- ALWAYS use `revalidateTag()` for fine-grained cache invalidation when possible

.cursor/rules/hydration-safety.mdc

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
description: React hydration mismatch prevention
3+
globs: ["**/*.tsx", "**/*.ts"]
4+
alwaysApply: false
5+
---
6+
7+
# Hydration Mismatch Prevention
8+
9+
## The Problem
10+
Hydration mismatches occur when server-rendered HTML doesn't match the client-rendered output, causing UI flickers, errors, and broken interactivity.
11+
12+
## Common AI Mistakes
13+
14+
### 1. Non-deterministic Values in Render
15+
❌ WRONG — different value on server vs client:
16+
```tsx
17+
export default function Component() {
18+
const id = Math.random() // Different on server vs client!
19+
return <div id={`item-${id}`}>Content</div>
20+
}
21+
```
22+
23+
✅ CORRECT — use useId() for unique IDs:
24+
```tsx
25+
import { useId } from 'react'
26+
export default function Component() {
27+
const id = useId()
28+
return <div id={id}>Content</div>
29+
}
30+
```
31+
32+
### 2. Browser APIs in Server-Rendered Code
33+
❌ WRONG — window/document don't exist on server:
34+
```tsx
35+
export default function Component() {
36+
const width = window.innerWidth // ReferenceError on server!
37+
return <div>{width}</div>
38+
}
39+
```
40+
41+
✅ CORRECT — guard with useEffect:
42+
```tsx
43+
'use client'
44+
import { useState, useEffect } from 'react'
45+
export default function Component() {
46+
const [width, setWidth] = useState(0)
47+
useEffect(() => { setWidth(window.innerWidth) }, [])
48+
return <div>{width}</div>
49+
}
50+
```
51+
52+
### 3. Date/Time Rendering
53+
❌ WRONG — different timezone on server vs client:
54+
```tsx
55+
export default function Component() {
56+
return <p>{new Date().toLocaleString()}</p> // Hydration mismatch!
57+
}
58+
```
59+
60+
✅ CORRECT — render dates client-side or use consistent formatting:
61+
```tsx
62+
'use client'
63+
import { useState, useEffect } from 'react'
64+
export default function DateDisplay({ date }: { date: string }) {
65+
const [formatted, setFormatted] = useState(date)
66+
useEffect(() => { setFormatted(new Date(date).toLocaleString()) }, [date])
67+
return <p>{formatted}</p>
68+
}
69+
```
70+
71+
## Rule
72+
- NEVER use Math.random(), crypto.randomUUID(), or Date.now() directly in render
73+
- NEVER access window, document, localStorage, or navigator without checking typeof window !== 'undefined'
74+
- Use React.useId() for dynamically generated IDs
75+
- Wrap browser-only logic in useEffect

0 commit comments

Comments
 (0)