Claude Code Plugins

Community-maintained marketplace

Feedback

next-data-rendering

@zacharyr0th/next-starter
1
0

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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:

// 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:

// 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:

// 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:

// 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:

// 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

// 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

import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      <h1>Page Title</h1>
      <Suspense fallback={<Skeleton />}>
        <AsyncComponent />
      </Suspense>
    </div>
  );
}

Revalidation Strategies

// 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

# 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