Next.js Rendering Cheat Sheet

Rendering Strategies Overview

Comparison Table

Strategy Build Time Request Time Data Freshness Performance Use Case
SSG ✅ Pre-rendered ✅ Instant ❌ Static 🟢 Best Blog, docs, marketing
ISR ✅ Pre-rendered ✅ Instant 🟡 Periodic 🟢 Best E-commerce, news
SSR ❌ Not built ⏱️ Server time ✅ Fresh 🟡 Good Dashboard, user content
CSR ❌ Not built ⏱️ Client time ✅ Fresh 🟡 Good Admin panels, SPAs

Static Site Generation (SSG)

Basic SSG

// app/blog/[slug]/page.js
export default async function BlogPost({ params }) {
    const post = await getPost(params.slug);
    
    return (
        <article>
            <h1>{post.title}</h1>
            <div>{post.content}</div>
        </article>
    );
}

// Generate static pages at build time
export async function generateStaticParams() {
    const posts = await getAllPosts();
    
    return posts.map((post) => ({
        slug: post.slug,
    }));
}

SSG with Data Fetching

// app/products/page.js
export default async function ProductsPage() {
    const products = await getProducts();
    
    return (
        <div>
            <h1>Products</h1>
            {products.map(product => (
                <div key={product.id}>
                    <h2>{product.name}</h2>
                    <p>{product.description}</p>
                </div>
            ))}
        </div>
    );
}

// Fetch data at build time
export async function generateStaticParams() {
    const products = await getProducts();
    
    return products.map((product) => ({
        id: product.id,
    }));
}

SSG with External Data

// app/news/page.js
export default async function NewsPage() {
    const news = await fetch('https://api.example.com/news', {
        next: { revalidate: 3600 } // Revalidate every hour
    }).then(res => res.json());
    
    return (
        <div>
            {news.map(article => (
                <article key={article.id}>
                    <h2>{article.title}</h2>
                    <p>{article.summary}</p>
                </article>
            ))}
        </div>
    );
}

Incremental Static Regeneration (ISR)

Time-Based Revalidation

// app/products/[id]/page.js
export default async function ProductPage({ params }) {
    const product = await getProduct(params.id);
    
    return (
        <div>
            <h1>{product.name}</h1>
            <p>{product.description}</p>
            <p>Price: ${product.price}</p>
        </div>
    );
}

// Revalidate every 60 seconds
export const revalidate = 60;

export async function generateStaticParams() {
    const products = await getProducts();
    
    return products.map((product) => ({
        id: product.id,
    }));
}

On-Demand Revalidation

// app/api/revalidate/route.js
import { revalidatePath, revalidateTag } from 'next/cache';

export async function POST(request) {
    const { path, tag } = await request.json();
    
    if (path) {
        revalidatePath(path);
    }
    
    if (tag) {
        revalidateTag(tag);
    }
    
    return Response.json({ revalidated: true });
}

// Usage in component
export default async function ProductPage({ params }) {
    const product = await fetch(`/api/products/${params.id}`, {
        next: { tags: ['product', `product-${params.id}`] }
    }).then(res => res.json());
    
    return <div>{/* product content */}</div>;
}

// Trigger revalidation
await fetch('/api/revalidate', {
    method: 'POST',
    body: JSON.stringify({ 
        path: `/products/${productId}`,
        tag: `product-${productId}`
    })
});

Conditional ISR

// app/blog/[slug]/page.js
export default async function BlogPost({ params }) {
    const post = await getPost(params.slug);
    
    return (
        <article>
            <h1>{post.title}</h1>
            <div>{post.content}</div>
        </article>
    );
}

export async function generateStaticParams() {
    const posts = await getAllPosts();
    
    return posts.map((post) => ({
        slug: post.slug,
    }));
}

// Different revalidation times based on post type
export async function generateMetadata({ params }) {
    const post = await getPost(params.slug);
    
    if (post.type === 'news') {
        // News posts revalidate every 5 minutes
        return { revalidate: 300 };
    } else if (post.type === 'tutorial') {
        // Tutorials revalidate every day
        return { revalidate: 86400 };
    }
    
    // Default: revalidate every hour
    return { revalidate: 3600 };
}

Server-Side Rendering (SSR)

Basic SSR

// app/dashboard/page.js
export default async function DashboardPage() {
    // This runs on every request
    const user = await getCurrentUser();
    const data = await fetchUserData(user.id);
    
    return (
        <div>
            <h1>Welcome, {user.name}</h1>
            <div>{/* dashboard content */}</div>
        </div>
    );
}

SSR with Dynamic Data

// app/search/page.js
export default async function SearchPage({ searchParams }) {
    const query = searchParams.q || '';
    const results = await searchProducts(query);
    
    return (
        <div>
            <h1>Search Results</h1>
            {results.map(result => (
                <div key={result.id}>
                    <h2>{result.name}</h2>
                    <p>{result.description}</p>
                </div>
            ))}
        </div>
    );
}

SSR with Authentication

// app/profile/page.js
import { redirect } from 'next/navigation';

export default async function ProfilePage() {
    const user = await getCurrentUser();
    
    if (!user) {
        redirect('/login');
    }
    
    const profile = await getUserProfile(user.id);
    
    return (
        <div>
            <h1>Profile</h1>
            <p>Name: {profile.name}</p>
            <p>Email: {profile.email}</p>
        </div>
    );
}

Client-Side Rendering (CSR)

Basic CSR

'use client';

import { useState, useEffect } from 'react';

export default function ClientComponent() {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    
    useEffect(() => {
        async function fetchData() {
            const response = await fetch('/api/data');
            const result = await response.json();
            setData(result);
            setLoading(false);
        }
        
        fetchData();
    }, []);
    
    if (loading) return <div>Loading...</div>;
    
    return (
        <div>
            <h1>Client-Side Data</h1>
            <pre>{JSON.stringify(data, null, 2)}</pre>
        </div>
    );
}

CSR with SWR

'use client';

import useSWR from 'swr';

const fetcher = (url) => fetch(url).then(res => res.json());

export default function SWRComponent() {
    const { data, error, isLoading } = useSWR('/api/data', fetcher, {
        refreshInterval: 5000, // Refresh every 5 seconds
        revalidateOnFocus: true
    });
    
    if (isLoading) return <div>Loading...</div>;
    if (error) return <div>Error: {error.message}</div>;
    
    return (
        <div>
            <h1>Real-time Data</h1>
            <pre>{JSON.stringify(data, null, 2)}</pre>
        </div>
    );
}

Hybrid Approach

// app/hybrid/page.js
import ClientComponent from './ClientComponent';

export default async function HybridPage() {
    // Server-side data
    const staticData = await getStaticData();
    
    return (
        <div>
            <h1>Hybrid Page</h1>
            
            {/* Server-rendered content */}
            <div>
                <h2>Static Content</h2>
                <p>{staticData.description}</p>
            </div>
            
            {/* Client-side content */}
            <ClientComponent />
        </div>
    );
}

Streaming and Suspense

Streaming with Suspense

import { Suspense } from 'react';

async function SlowComponent() {
    await new Promise(resolve => setTimeout(resolve, 2000));
    const data = await fetch('https://api.example.com/slow-data');
    return data.json();
}

export default function StreamingPage() {
    return (
        <div>
            <h1>Streaming Page</h1>
            
            {/* Fast content renders immediately */}
            <div>
                <h2>Fast Content</h2>
                <p>This content loads immediately.</p>
            </div>
            
            {/* Slow content streams in */}
            <Suspense fallback={<div>Loading slow content...</div>}>
                <SlowComponent />
            </Suspense>
        </div>
    );
}

Parallel Data Fetching

// app/dashboard/page.js
import { Suspense } from 'react';

async function UserProfile({ userId }) {
    const user = await fetch(`/api/users/${userId}`);
    return user.json();
}

async function UserPosts({ userId }) {
    const posts = await fetch(`/api/users/${userId}/posts`);
    return posts.json();
}

async function UserStats({ userId }) {
    const stats = await fetch(`/api/users/${userId}/stats`);
    return stats.json();
}

export default function DashboardPage({ params }) {
    return (
        <div>
            <h1>Dashboard</h1>
            
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '1rem' }}>
                <Suspense fallback={<div>Loading profile...</div>}>
                    <UserProfile userId={params.userId} />
                </Suspense>
                
                <Suspense fallback={<div>Loading posts...</div>}>
                    <UserPosts userId={params.userId} />
                </Suspense>
                
                <Suspense fallback={<div>Loading stats...</div>}>
                    <UserStats userId={params.userId} />
                </Suspense>
            </div>
        </div>
    );
}

Route Segment Config

Page-Level Configuration

// app/blog/[slug]/page.js
export const dynamic = 'force-static'; // Force SSG
export const revalidate = 3600; // Revalidate every hour
export const fetchCache = 'force-cache'; // Always cache
export const runtime = 'nodejs'; // Use Node.js runtime

export default function BlogPost({ params }) {
    // Component implementation
}

Layout-Level Configuration

// app/dashboard/layout.js
export const dynamic = 'force-dynamic'; // Force SSR
export const revalidate = 0; // No caching

export default function DashboardLayout({ children }) {
    return (
        <div>
            <nav>Dashboard Navigation</nav>
            {children}
        </div>
    );
}

Performance Optimization

Bundle Analysis

// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
    enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer({
    // your existing config
});

// Run: ANALYZE=true npm run build

Code Splitting

import dynamic from 'next/dynamic';

// Lazy load heavy components
const HeavyChart = dynamic(() => import('./HeavyChart'), {
    ssr: false,
    loading: () => <div>Loading chart...</div>
});

const MapComponent = dynamic(() => import('./MapComponent'), {
    ssr: false,
    loading: () => <div>Loading map...</div>
});

export default function Page() {
    return (
        <div>
            <h1>Page with Heavy Components</h1>
            <HeavyChart />
            <MapComponent />
        </div>
    );
}

Preloading Strategies

'use client';

import { useEffect } from 'react';

export default function PreloadComponent() {
    useEffect(() => {
        // Preload critical resources
        const link = document.createElement('link');
        link.rel = 'preload';
        link.href = '/api/critical-data';
        link.as = 'fetch';
        document.head.appendChild(link);
    }, []);
    
    return <div>Component with preloading</div>;
}

Best Practices

Choosing the Right Strategy

// ✅ Use SSG for:
// - Marketing pages
// - Blog posts
// - Documentation
// - Product catalogs (with ISR)

// ✅ Use SSR for:
// - User-specific content
// - Real-time data
// - Search results
// - Dashboards

// ✅ Use CSR for:
// - Admin panels
// - Interactive tools
// - Real-time updates
// - Complex state management

Error Handling

// app/error.js
'use client';

export default function Error({ error, reset }) {
    return (
        <div>
            <h2>Something went wrong!</h2>
            <p>{error.message}</p>
            <button onClick={reset}>Try again</button>
        </div>
    );
}

// app/global-error.js
export default function GlobalError({ error, reset }) {
    return (
        <html>
            <body>
                <h2>Something went wrong!</h2>
                <button onClick={reset}>Try again</button>
            </body>
        </html>
    );
}

Loading States

// app/loading.js
export default function Loading() {
    return (
        <div className="loading">
            <div className="spinner"></div>
            <p>Loading...</p>
        </div>
    );
}

// app/not-found.js
export default function NotFound() {
    return (
        <div>
            <h2>Page Not Found</h2>
            <p>Could not find the requested resource.</p>
        </div>
    );
}

Performance Monitoring

// lib/performance.js
export function measurePageLoad() {
    if (typeof window !== 'undefined') {
        const navigation = performance.getEntriesByType('navigation')[0];
        
        return {
            dns: navigation.domainLookupEnd - navigation.domainLookupStart,
            tcp: navigation.connectEnd - navigation.connectStart,
            ttfb: navigation.responseStart - navigation.requestStart,
            domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
            load: navigation.loadEventEnd - navigation.loadEventStart,
        };
    }
}

// Usage in component
'use client';

import { useEffect } from 'react';
import { measurePageLoad } from '@/lib/performance';

export default function PerformanceMonitor() {
    useEffect(() => {
        const metrics = measurePageLoad();
        console.log('Page Load Metrics:', metrics);
        
        // Send to analytics
        fetch('/api/analytics/performance', {
            method: 'POST',
            body: JSON.stringify(metrics)
        });
    }, []);
    
    return null;
}