Next.js SSR and SSG Concepts

Loading

Next.js offers powerful rendering strategies that go beyond traditional client-side React applications. Understanding Server-Side Rendering (SSR) and Static Site Generation (SSG) is crucial for building performant, SEO-friendly applications.

Core Rendering Strategies

1. Static Site Generation (SSG)

Pre-renders pages at build time – ideal for content that doesn’t change frequently.

Basic SSG with getStaticProps

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();

  return {
    props: { posts }, // Will be passed to page component
    revalidate: 60 // Optional: Enable ISR (re-generate every 60s)
  };
}

function Blog({ posts }) {
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

export default Blog;

Dynamic Routes with SSG (getStaticPaths)

export async function getStaticPaths() {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();

  const paths = posts.map(post => ({
    params: { id: post.id.toString() }
  }));

  return { paths, fallback: false }; // or 'blocking' or true
}

export async function getStaticProps({ params }) {
  const res = await fetch(`https://api.example.com/posts/${params.id}`);
  const post = await res.json();

  return { props: { post } };
}

function Post({ post }) {
  return <article>{post.title}</article>;
}

export default Post;

2. Server-Side Rendering (SSR)

Renders pages on each request – ideal for personalized or frequently updated content.

Basic SSR with getServerSideProps

export async function getServerSideProps(context) {
  // Context contains request-specific params
  const { req, res, params, query } = context;

  const userAgent = req.headers['user-agent'];
  const res = await fetch(`https://api.example.com/data?user=${query.user}`);
  const data = await res.json();

  return {
    props: { data, userAgent } // Passed to page component
  };
}

function Page({ data, userAgent }) {
  return (
    <div>
      <p>User Agent: {userAgent}</p>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default Page;

Advanced Patterns

1. Incremental Static Regeneration (ISR)

Update static content without rebuilding the entire site.

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/products');
  const products = await res.json();

  return {
    props: { products },
    revalidate: 3600 // Re-generate every hour if requests come in
  };
}

2. Hybrid Rendering

Combine SSG, SSR, and client-side fetching in a single app.

// pages/index.js - SSG for homepage
export async function getStaticProps() { /* ... */ }

// pages/blog/[slug].js - SSG for blog posts
export async function getStaticPaths() { /* ... */ }
export async function getStaticProps() { /* ... */ }

// pages/dashboard.js - SSR for authenticated content
export async function getServerSideProps(context) { /* ... */ }

// pages/search.js - Client-side data fetching
function SearchPage() {
  const [results, setResults] = useState([]);

  const handleSearch = async (query) => {
    const res = await fetch(`/api/search?q=${query}`);
    setResults(await res.json());
  };

  return ( /* ... */ );
}

3. On-Demand Revalidation

Trigger static page updates via API routes.

// pages/api/revalidate.js
export default async function handler(req, res) {
  if (req.query.secret !== process.env.REVALIDATE_TOKEN) {
    return res.status(401).json({ message: 'Invalid token' });
  }

  try {
    await res.revalidate('/path-to-revalidate');
    return res.json({ revalidated: true });
  } catch (err) {
    return res.status(500).send('Error revalidating');
  }
}

// Then call from CMS webhook or manually:
// POST /api/revalidate?secret=<token>

Data Fetching Methods Comparison

MethodWhen to UseExecution TimeData Freshness
getStaticPropsContent known at build timeBuild timeStale until rebuild
getStaticProps+ISRContent that updates periodicallyBuild + runtimeConfigurable refresh
getServerSidePropsPersonalized/private dataEach requestAlways fresh
Client-side fetchingUser-specific, non-SEO critical dataClient navigationAlways fresh

Performance Optimization

1. Static Assets Optimization

import Image from 'next/image';

function OptimizedImage() {
  return (
    <Image
      src="/profile.jpg"
      alt="Profile"
      width={500}
      height={500}
      priority // For above-the-fold images
      placeholder="blur" // Optional blur-up
      blurDataURL="data:image/png;base64,..."
    />
  );
}

2. Dynamic Imports (Code Splitting)

import dynamic from 'next/dynamic';

const HeavyComponent = dynamic(
  () => import('../components/HeavyComponent'),
  { 
    loading: () => <p>Loading...</p>,
    ssr: false // Disable SSR for this component if needed
  }
);

function Page() {
  return <HeavyComponent />;
}

3. API Route Caching

// pages/api/data.js
export default async function handler(req, res) {
  // Set cache headers
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=60, stale-while-revalidate=300'
  );

  const data = await fetchData();
  res.json(data);
}

TypeScript Integration

1. Typed Page Props

import { GetStaticProps, GetServerSideProps } from 'next';

interface Post {
  id: number;
  title: string;
  content: string;
}

interface PageProps {
  posts: Post[];
}

export const getStaticProps: GetStaticProps<PageProps> = async () => {
  const res = await fetch('https://api.example.com/posts');
  const posts: Post[] = await res.json();

  return {
    props: { posts }
  };
};

function BlogPage({ posts }: PageProps) {
  return ( /* ... */ );
}

export default BlogPage;

2. Typed API Routes

import type { NextApiRequest, NextApiResponse } from 'next';

type ResponseData = {
  success: boolean;
  data?: any;
  error?: string;
};

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ success: false, error: 'Method not allowed' });
  }

  // Process request
  res.status(200).json({ success: true, data: {} });
}

Best Practices

  1. Page Classification: Analyze each page’s needs to choose the right strategy
  2. ISR for Dynamic Content: Prefer over SSR when possible for better performance
  3. Client-side Complements: Use SWR or React Query for client-side data updates
  4. Layout Components: Use _app.js for shared layouts to avoid remounting
  5. Error Handling: Implement proper error boundaries and status codes
  6. Security: Validate all API inputs and sanitize outputs
  7. Monitoring: Track cache hit ratios and SSR performance

Real-World Examples

1. E-commerce Site

// Homepage - SSG with ISR
export async function getStaticProps() {
  const featured = await getFeaturedProducts();
  return { props: { featured }, revalidate: 3600 };
}

// Product pages - SSG with dynamic paths
export async function getStaticPaths() {
  const products = await getAllProductIds();
  return { paths: products.map(id => ({ params: { id } })), fallback: 'blocking' };
}

// Cart page - SSR for user-specific data
export async function getServerSideProps({ req }) {
  const cart = await getUserCart(req.cookies.userId);
  return { props: { cart } };
}

2. Content Management System

// Blog index - SSG
export async function getStaticProps() {
  const posts = await getAllPosts();
  return { props: { posts } };
}

// Blog posts - SSG with on-demand revalidation
export async function getStaticProps({ params }) {
  const post = await getPostBySlug(params.slug);
  return { props: { post }, revalidate: 60 };
}

// Dashboard - SSR
export async function getServerSideProps(context) {
  const session = await getSession(context);
  if (!session) {
    return { redirect: { destination: '/login', permanent: false } };
  }
  return { props: { user: session.user } };
}

Understanding these Next.js rendering concepts allows you to build applications that are both performant and deliver excellent user experiences across different types of content and use cases.

Leave a Reply

Your email address will not be published. Required fields are marked *