Use when implementing rendering strategies like SSG, SSR, ISR, CSR, or managing caching and revalidation. Invoke for static generation, server-side rendering, incremental static regeneration, partial prerendering, or cache optimization.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add mattnigh/skills_collection --skill collection --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Collection?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/mattnigh-collection-4324742e)More formats (shields.io, HTML) on the badges page.
---
name: next-data-rendering
description: Use when implementing rendering strategies like SSG, SSR, ISR, CSR, or managing caching and revalidation. Invoke for static generation, server-side rendering, incremental static regeneration, partial prerendering, or cache optimization.
allowed-tools: Read, Grep, Glob
---
# Next.js Data Rendering Expert
## Purpose
Expert knowledge of Next.js rendering strategies and data fetching patterns. Covers SSG (Static Site Generation), SSR (Server-Side Rendering), ISR (Incremental Static Regeneration), CSR (Client-Side Rendering), caching strategies, and revalidation.
## When to Use
Invoke this skill when:
- Choosing rendering strategies for pages
- Implementing caching strategies
- Setting up ISR with revalidation
- Implementing Partial Prerendering (PPR)
- Optimizing page load performance
- Managing cache invalidation
- Implementing streaming and Suspense
- Debugging caching or stale data issues
- Balancing static and dynamic content
## Documentation Available
**Location**: `/Users/zach/Documents/cc-skills/docs/next/`
**Coverage** (~60 files):
- **Rendering Strategies**:
- Static Site Generation (SSG)
- Server-Side Rendering (SSR)
- Incremental Static Regeneration (ISR)
- Client-Side Rendering (CSR)
- Partial Prerendering (PPR - experimental)
- **Caching**:
- Request memoization
- Data cache
- Full route cache
- Router cache
- Cache configuration
- **Revalidation**:
- Time-based revalidation
- On-demand revalidation
- Tag-based revalidation
## Related Skills
- **next-app-router**: For app/ directory implementation
- **next-pages-router**: For pages/ directory implementation
- **next-config-optimization**: For build optimization
## Best Practices
- **Default to static** - Use SSG for best performance
- **Use ISR for freshness** - Balance static and dynamic
- **Implement proper cache headers** - Control CDN caching
- **Use tag-based revalidation** - Granular cache invalidation
- **Leverage streaming** - Show content progressively
- **Minimize dynamic rendering** - Static is faster
- **Use Partial Prerendering** - Mix static and dynamic
- **Cache at multiple levels** - Request, data, route, router
- **Set appropriate revalidation times** - Based on data freshness needs
## Examples
### Example 1: Static Site Generation (SSG)
**Question**: "How do I implement SSG with Next.js?"
**Response**:
```typescript
// App Router (app/products/page.tsx)
export const dynamic = 'force-static'; // Explicitly static (default)
async function ProductsPage() {
// Fetched at build time
const products = await fetch('https://api.example.com/products', {
cache: 'force-cache', // SSG (default)
}).then(r => r.json());
return (
<div>
<h1>Products (Built at: {new Date().toISOString()})</h1>
<ul>
{products.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</div>
);
}
export default ProductsPage;
// Pages Router (pages/products.tsx)
import { GetStaticProps } from 'next';
export const getStaticProps: GetStaticProps = async () => {
const products = await fetch('https://api.example.com/products')
.then(r => r.json());
return {
props: { products },
};
};
```
**Benefits**:
- Fastest performance (served from CDN)
- SEO-friendly (pre-rendered HTML)
- Low server load
- Works with static hosting
**Use Cases**:
- Marketing pages
- Blog posts
- Documentation
- Product catalogs (with ISR)
**References**:
- See: `docs/next/app/building-your-application/rendering/server-components/`
### Example 2: Incremental Static Regeneration (ISR)
**Question**: "How do I implement ISR for fresh data?"
**Response**:
```typescript
// App Router (app/products/page.tsx)
export const revalidate = 3600; // Revalidate every hour
async function ProductsPage() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }, // Per-request revalidation
}).then(r => r.json());
return (
<div>
<h1>Products</h1>
<p>Last updated: {new Date().toISOString()}</p>
<ul>
{products.map(p => (
<li key={p.id}>{p.name} - ${p.price}</li>
))}
</ul>
</div>
);
}
// Different revalidation times for different requests
async function HomePage() {
const [products, posts] = await Promise.all([
fetch('https://api.example.com/products', {
next: { revalidate: 3600 }, // 1 hour
}).then(r => r.json()),
fetch('https://api.example.com/posts', {
next: { revalidate: 60 }, // 1 minute
}).then(r => r.json()),
]);
return <div>{/* Content */}</div>;
}
// Pages Router (pages/products.tsx)
export const getStaticProps: GetStaticProps = async () => {
const products = await fetch('https://api.example.com/products')
.then(r => r.json());
return {
props: { products },
revalidate: 3600, // Revalidate every hour
};
};
// On-demand revalidation (App Router)
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
const secret = request.nextUrl.searchParams.get('secret');
if (secret !== process.env.REVALIDATE_SECRET) {
return Response.json({ message: 'Invalid secret' }, { status: 401 });
}
// Revalidate specific path
revalidatePath('/products');
// Or revalidate by tag
revalidateTag('products');
return Response.json({ revalidated: true, now: Date.now() });
}
// Tag-based revalidation
async function getProducts() {
const products = await fetch('https://api.example.com/products', {
next: { tags: ['products'] }, // Tag for revalidation
}).then(r => r.json());
return products;
}
```
**ISR Workflow**:
1. Build time: Generate static pages
2. Request: Serve stale page instantly
3. Background: Regenerate if expired
4. Next request: Serve fresh page
**Benefits**:
- Static performance
- Always fresh data
- Automatic regeneration
- No rebuild needed
**References**:
- See: `docs/next/app/building-your-application/data-fetching/fetching-caching-and-revalidating/`
### Example 3: Server-Side Rendering (SSR)
**Question**: "How do I implement SSR for dynamic content?"
**Response**:
```typescript
// App Router (app/dashboard/page.tsx)
export const dynamic = 'force-dynamic'; // Force SSR
export const revalidate = 0; // Don't cache
async function DashboardPage() {
// Fetched on every request
const user = await fetch('https://api.example.com/user', {
cache: 'no-store', // SSR
headers: {
Cookie: cookies().toString(), // Access request cookies
},
}).then(r => r.json());
const stats = await fetch('https://api.example.com/stats', {
cache: 'no-store',
}).then(r => r.json());
return (
<div>
<h1>Welcome, {user.name}</h1>
<p>Total Sales: ${stats.totalSales}</p>
<p>Generated at: {new Date().toISOString()}</p>
</div>
);
}
// Access request data
import { cookies, headers } from 'next/headers';
async function ProfilePage() {
const cookieStore = cookies();
const token = cookieStore.get('token');
const headersList = headers();
const userAgent = headersList.get('user-agent');
// Fetch based on request data
const user = await fetch('https://api.example.com/user', {
cache: 'no-store',
headers: {
Authorization: `Bearer ${token?.value}`,
},
}).then(r => r.json());
return <div>Hello, {user.name}</div>;
}
// Pages Router (pages/dashboard.tsx)
import { GetServerSideProps } from 'next';
export const getServerSideProps: GetServerSideProps = async (context) => {
const { req, res } = context;
const user = await fetch('https://api.example.com/user', {
headers: {
Cookie: req.headers.cookie || '',
},
}).then(r => r.json());
// Set cache headers
res.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
);
return {
props: { user },
};
};
```
**When to Use SSR**:
- User-specific content
- Highly dynamic data
- Request-dependent data (cookies, headers)
- Real-time data
**Drawbacks**:
- Slower than static
- Higher server load
- No CDN caching (by default)
**References**:
- See: `docs/next/app/building-your-application/rendering/server-components/`
### Example 4: Client-Side Rendering (CSR)
**Question**: "How do I implement CSR for interactive data?"
**Response**:
```typescript
// App Router (app/products/product-list.tsx)
'use client';
import { useState, useEffect } from 'react';
import useSWR from 'swr';
// Traditional useEffect approach
export function ProductListEffect() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/products')
.then(r => r.json())
.then(data => {
setProducts(data);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
// SWR approach (recommended)
const fetcher = (url: string) => fetch(url).then(r => r.json());
export function ProductListSWR() {
const { data, error, isLoading, mutate } = useSWR('/api/products', fetcher, {
revalidateOnFocus: true,
revalidateOnReconnect: true,
refreshInterval: 30000, // Poll every 30s
});
if (error) return <div>Failed to load</div>;
if (isLoading) return <div>Loading...</div>;
return (
<div>
<button onClick={() => mutate()}>Refresh</button>
<ul>
{data.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</div>
);
}
// React Query approach
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function ProductListQuery() {
const queryClient = useQueryClient();
const { data, isLoading, error } = useQuery({
queryKey: ['products'],
queryFn: () => fetch('/api/products').then(r => r.json()),
staleTime: 5000, // Consider fresh for 5s
cacheTime: 10 * 60 * 1000, // Cache for 10 minutes
});
const mutation = useMutation({
mutationFn: (newProduct) =>
fetch('/api/products', {
method: 'POST',
body: JSON.stringify(newProduct),
}).then(r => r.json()),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['products'] });
},
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading products</div>;
return <ul>{/* Products */}</ul>;
}
```
**When to Use CSR**:
- User interactions
- Real-time updates
- Personalized content
- Non-SEO content
**Benefits**:
- Interactive
- No server load
- Real-time updates
- Automatic revalidation (with SWR/React Query)
**References**:
- See: `docs/next/app/building-your-application/rendering/client-components/`
### Example 5: Partial Prerendering (PPR)
**Question**: "How do I use Partial Prerendering?"
**Response**:
```typescript
// next.config.js
module.exports = {
experimental: {
ppr: true, // Enable PPR
},
};
// app/products/page.tsx
import { Suspense } from 'react';
// Static shell
export default function ProductsPage() {
return (
<div>
<h1>Products</h1>
<p>This header is static</p>
{/* Dynamic content with Suspense */}
<Suspense fallback={<ProductsSkeleton />}>
<ProductList />
</Suspense>
{/* Another dynamic section */}
<Suspense fallback={<ReviewsSkeleton />}>
<RecentReviews />
</Suspense>
<footer>This footer is static</footer>
</div>
);
}
// Dynamic components (Server Components)
async function ProductList() {
const products = await fetch('https://api.example.com/products', {
cache: 'no-store', // Dynamic
}).then(r => r.json());
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
async function RecentReviews() {
const reviews = await fetch('https://api.example.com/reviews', {
cache: 'no-store',
}).then(r => r.json());
return (
<div>
{reviews.map(r => (
<div key={r.id}>{r.content}</div>
))}
</div>
);
}
```
**PPR Benefits**:
- Instant static shell
- Streaming dynamic content
- Best of both worlds
- SEO-friendly
- Fast perceived performance
**How it Works**:
1. Serve static shell instantly
2. Stream dynamic content as it loads
3. Replace Suspense fallbacks progressively
**References**:
- See: `docs/next/app/api-reference/next-config-js/partial-prerendering/`
## Common Patterns
### Cache Configuration
```typescript
// Opt into caching
fetch('https://api.example.com/data', {
cache: 'force-cache', // Default
});
// Opt out of caching
fetch('https://api.example.com/data', {
cache: 'no-store',
});
// ISR
fetch('https://api.example.com/data', {
next: { revalidate: 3600 },
});
// Tag-based caching
fetch('https://api.example.com/data', {
next: { tags: ['products'] },
});
```
### Streaming with Suspense
```typescript
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<h1>Page Title</h1>
<Suspense fallback={<Skeleton />}>
<AsyncComponent />
</Suspense>
</div>
);
}
```
### Revalidation Strategies
```typescript
// Time-based (page-level)
export const revalidate = 3600;
// Time-based (request-level)
fetch(url, { next: { revalidate: 3600 } });
// On-demand (path)
revalidatePath('/products');
// On-demand (tag)
revalidateTag('products');
```
## Search Helpers
```bash
# Find rendering docs
grep -r "SSG\|SSR\|ISR\|rendering" /Users/zach/Documents/cc-skills/docs/next/
# Find caching docs
grep -r "cache\|revalidate\|fetch" /Users/zach/Documents/cc-skills/docs/next/
# Find streaming docs
grep -r "streaming\|Suspense\|PPR" /Users/zach/Documents/cc-skills/docs/next/
# List rendering files
ls /Users/zach/Documents/cc-skills/docs/next/app/building-your-application/rendering/
```
## Common Errors
- **Data is stale**: Cache not revalidating
- Solution: Set appropriate revalidate time or use `cache: 'no-store'`
- **Page is slow**: Using SSR unnecessarily
- Solution: Switch to ISR with revalidation
- **Build fails**: Too many static pages
- Solution: Use `fallback: 'blocking'` in getStaticPaths
- **Cache not working**: Dynamic functions in static page
- Solution: Remove cookies(), headers(), or searchParams access
## Performance Comparison
| Strategy | Speed | Freshness | Server Load | SEO | Use Case |
|----------|-------|-----------|-------------|-----|----------|
| SSG | Fastest | Static | None | Best | Marketing, blogs |
| ISR | Fast | Fresh | Low | Best | Product pages |
| SSR | Slow | Real-time | High | Good | Dashboards |
| CSR | Instant shell | Real-time | None | Poor | User interactions |
| PPR | Fast | Mixed | Low | Best | E-commerce |
## Caching Layers
1. **Request Memoization**: Dedupe same requests in single render
2. **Data Cache**: Persist fetch results across requests
3. **Full Route Cache**: Pre-rendered HTML + RSC payload
4. **Router Cache**: Client-side cached routes
## Notes
- Documentation covers Next.js 13-14
- App Router defaults to SSG
- fetch() is extended with caching options
- Partial Prerendering is experimental (Next.js 14+)
- ISR works with static hosting (Vercel, Netlify)
- Tag-based revalidation enables granular cache control
- File paths reference local documentation cache
- For latest updates, check https://nextjs.org/docs/app/building-your-application/rendering
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!