How to Create Dynamic Pages in Next.js: A Complete Developer's Guide - Techvblogs

How to Create Dynamic Pages in Next.js: A Complete Developer's Guide

Master dynamic pages Next.js with file-based routing, SSG, SSR, and ISR. Includes TypeScript examples, API integration, and performance optimization.


Suresh Ramani - Author - Techvblogs
Suresh Ramani
 

6 days ago

TechvBlogs - Google News

Dynamic pages are the backbone of modern web applications, enabling developers to create scalable, content-driven websites that adapt to user needs and data changes. Next.js provides powerful tools for building dynamic pages Next.js applications that combine server-side rendering, static generation, and client-side functionality seamlessly. This comprehensive guide will teach you everything you need to know about implementing dynamic pages Next.js solutions, from basic routing to advanced data fetching strategies.

Understanding Dynamic Pages in Next.js

Dynamic pages Next.js implementations allow developers to create flexible routing systems that generate pages based on parameters, database content, or external APIs. Unlike static pages that display the same content for every user, dynamic pages adapt their content based on URL parameters, user authentication status, or real-time data.

Next.js handles dynamic pages through its file-based routing system, where brackets [param] in file names create dynamic routes. This approach eliminates the need for complex routing configurations while providing powerful features like automatic code splitting, prefetching, and optimized performance out of the box.

The framework supports multiple rendering methods for dynamic pages Next.js applications: Static Site Generation (SSG), Server-Side Rendering (SSR), and Client-Side Rendering (CSR). Each method serves different use cases and performance requirements, making Next.js incredibly versatile for various project types.

Setting Up Your Next.js Project

Before diving into dynamic pages Next.js implementation, let’s establish a solid foundation with proper project setup and structure.

Project Initialization

Create a new Next.js project with the latest features and TypeScript support:

# Create new Next.js project
npx create-next-app@latest dynamic-pages-app --typescript --tailwind --eslint --app

# Navigate to project directory
cd dynamic-pages-app

# Install additional dependencies for dynamic content
npm install axios date-fns
npm install -D @types/node

# Start development server
npm run dev

Project Structure for Dynamic Pages

Organize your project structure to support scalable dynamic pages Next.js development:

src/
├── app/
│   ├── globals.css
│   ├── layout.tsx
│   ├── page.tsx
│   ├── blog/
│   │   ├── page.tsx
│   │   └── [slug]/
│   │       └── page.tsx
│   ├── products/
│   │   ├── page.tsx
│   │   ├── [id]/
│   │   │   └── page.tsx
│   │   └── category/
│   │       └── [category]/
│   │           └── page.tsx
│   └── users/
│       └── [userId]/
│           ├── page.tsx
│           └── posts/
│               └── [postId]/
│                   └── page.tsx
├── components/
├── lib/
├── types/
└── utils/

This structure demonstrates how dynamic pages Next.js applications can handle various routing patterns, from simple parameter-based routes to complex nested dynamic routes.

Basic Dynamic Routing Implementation

Let’s start with fundamental dynamic pages Next.js routing patterns that form the foundation of most applications.

Single Dynamic Route

Create a basic product detail page that responds to dynamic product IDs:

// app/products/[id]/page.tsx
import { notFound } from 'next/navigation';

interface Product {
  id: string;
  name: string;
  description: string;
  price: number;
  image: string;
  category: string;
}

// Mock data - replace with actual API calls
const products: Product[] = [
  {
    id: '1',
    name: 'Wireless Headphones',
    description: 'High-quality wireless headphones with noise cancellation',
    price: 199.99,
    image: '/images/headphones.jpg',
    category: 'Electronics'
  },
  {
    id: '2',
    name: 'Smart Watch',
    description: 'Feature-rich smartwatch with health monitoring',
    price: 299.99,
    image: '/images/smartwatch.jpg',
    category: 'Electronics'
  }
];

async function getProduct(id: string): Promise<Product | null> {
  // Simulate API delay
  await new Promise(resolve => setTimeout(resolve, 100));
  
  return products.find(product => product.id === id) || null;
}

export default async function ProductPage({ 
  params 
}: { 
  params: { id: string } 
}) {
  const product = await getProduct(params.id);

  if (!product) {
    notFound();
  }

  return (
    <div className="container mx-auto px-4 py-8">
      <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
        <div className="aspect-square bg-gray-200 rounded-lg flex items-center justify-center">
          <span className="text-gray-500">Product Image</span>
        </div>
        
        <div className="space-y-6">
          <div>
            <span className="text-sm text-blue-600 font-medium">
              {product.category}
            </span>
            <h1 className="text-3xl font-bold text-gray-900 mt-1">
              {product.name}
            </h1>
          </div>
          
          <p className="text-gray-600 leading-relaxed">
            {product.description}
          </p>
          
          <div className="flex items-center justify-between">
            <span className="text-3xl font-bold text-gray-900">
              ${product.price}
            </span>
            <button className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors">
              Add to Cart
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// Generate metadata for SEO
export async function generateMetadata({ 
  params 
}: { 
  params: { id: string } 
}) {
  const product = await getProduct(params.id);
  
  if (!product) {
    return {
      title: 'Product Not Found'
    };
  }

  return {
    title: `${product.name} - Our Store`,
    description: product.description,
    openGraph: {
      title: product.name,
      description: product.description,
      images: [product.image],
    },
  };
}

This dynamic pages Next.js example demonstrates how to create a product detail page that adapts to different product IDs while maintaining proper SEO optimization and error handling.

Nested Dynamic Routes

Create more complex routing patterns with nested dynamic pages Next.js structures:

// app/users/[userId]/posts/[postId]/page.tsx
interface User {
  id: string;
  name: string;
  email: string;
}

interface Post {
  id: string;
  title: string;
  content: string;
  userId: string;
  createdAt: Date;
  tags: string[];
}

async function getUser(userId: string): Promise<User | null> {
  // Simulate API call
  const users: User[] = [
    { id: '1', name: 'John Doe', email: '[email protected]' },
    { id: '2', name: 'Jane Smith', email: '[email protected]' }
  ];
  
  return users.find(user => user.id === userId) || null;
}

async function getUserPost(userId: string, postId: string): Promise<Post | null> {
  // Simulate API call
  const posts: Post[] = [
    {
      id: '1',
      title: 'Getting Started with Next.js',
      content: 'Next.js is a powerful React framework that makes building web applications easier...',
      userId: '1',
      createdAt: new Date('2024-01-15'),
      tags: ['Next.js', 'React', 'Web Development']
    },
    {
      id: '2',
      title: 'Dynamic Routing Best Practices',
      content: 'When building dynamic pages, it\'s important to consider performance and SEO...',
      userId: '1',
      createdAt: new Date('2024-01-20'),
      tags: ['Routing', 'Performance', 'SEO']
    }
  ];
  
  return posts.find(post => post.id === postId && post.userId === userId) || null;
}

export default async function UserPostPage({
  params
}: {
  params: { userId: string; postId: string }
}) {
  const [user, post] = await Promise.all([
    getUser(params.userId),
    getUserPost(params.userId, params.postId)
  ]);

  if (!user || !post) {
    notFound();
  }

  return (
    <article className="container mx-auto px-4 py-8 max-w-4xl">
      <div className="mb-8">
        <div className="flex items-center space-x-2 text-sm text-gray-600 mb-4">
          <span>By {user.name}</span>
          <span>•</span>
          <time dateTime={post.createdAt.toISOString()}>
            {post.createdAt.toLocaleDateString()}
          </time>
        </div>
        
        <h1 className="text-4xl font-bold text-gray-900 mb-4">
          {post.title}
        </h1>
        
        <div className="flex flex-wrap gap-2 mb-6">
          {post.tags.map((tag) => (
            <span
              key={tag}
              className="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm"
            >
              {tag}
            </span>
          ))}
        </div>
      </div>
      
      <div className="prose prose-lg max-w-none">
        <p>{post.content}</p>
      </div>
    </article>
  );
}

export async function generateMetadata({
  params
}: {
  params: { userId: string; postId: string }
}) {
  const [user, post] = await Promise.all([
    getUser(params.userId),
    getUserPost(params.userId, params.postId)
  ]);

  if (!user || !post) {
    return {
      title: 'Post Not Found'
    };
  }

  return {
    title: `${post.title} by ${user.name}`,
    description: post.content.substring(0, 160),
    authors: [{ name: user.name }],
    keywords: post.tags,
  };
}

Static Site Generation with Dynamic Pages

One of the most powerful features of dynamic pages Next.js applications is the ability to pre-generate pages at build time for optimal performance.

Implementing generateStaticParams

For dynamic pages that can be pre-rendered, use generateStaticParams to specify which pages should be generated at build time:

// app/blog/[slug]/page.tsx
interface BlogPost {
  slug: string;
  title: string;
  content: string;
  excerpt: string;
  publishedAt: Date;
  author: string;
  coverImage: string;
  tags: string[];
}

// Mock blog data - replace with CMS or API calls
const blogPosts: BlogPost[] = [
  {
    slug: 'dynamic-pages-nextjs-guide',
    title: 'Complete Guide to Dynamic Pages in Next.js',
    content: 'Building dynamic pages in Next.js is essential for creating scalable web applications...',
    excerpt: 'Learn how to create dynamic pages in Next.js with this comprehensive guide.',
    publishedAt: new Date('2024-01-15'),
    author: 'Tech Writer',
    coverImage: '/images/blog/dynamic-pages.jpg',
    tags: ['Next.js', 'React', 'Web Development']
  },
  {
    slug: 'nextjs-performance-optimization',
    title: 'Next.js Performance Optimization Techniques',
    content: 'Performance is crucial for user experience and SEO. Here are the best practices...',
    excerpt: 'Discover essential techniques for optimizing Next.js application performance.',
    publishedAt: new Date('2024-01-20'),
    author: 'Performance Expert',
    coverImage: '/images/blog/performance.jpg',
    tags: ['Performance', 'Optimization', 'Next.js']
  }
];

async function getBlogPost(slug: string): Promise<BlogPost | null> {
  return blogPosts.find(post => post.slug === slug) || null;
}

// Generate static paths for all blog posts
export async function generateStaticParams() {
  return blogPosts.map((post) => ({
    slug: post.slug,
  }));
}

export default async function BlogPostPage({
  params
}: {
  params: { slug: string }
}) {
  const post = await getBlogPost(params.slug);

  if (!post) {
    notFound();
  }

  return (
    <article className="container mx-auto px-4 py-8 max-w-4xl">
      <header className="mb-10">
        <div className="aspect-video bg-gray-200 rounded-lg mb-6 flex items-center justify-center">
          <span className="text-gray-500">Cover Image</span>
        </div>
        
        <div className="space-y-4">
          <div className="flex flex-wrap gap-2">
            {post.tags.map((tag) => (
              <span
                key={tag}
                className="bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm"
              >
                {tag}
              </span>
            ))}
          </div>
          
          <h1 className="text-4xl md:text-5xl font-bold text-gray-900 leading-tight">
            {post.title}
          </h1>
          
          <div className="flex items-center text-gray-600 space-x-4">
            <span>By {post.author}</span>
            <span>•</span>
            <time dateTime={post.publishedAt.toISOString()}>
              {post.publishedAt.toLocaleDateString('en-US', {
                year: 'numeric',
                month: 'long',
                day: 'numeric'
              })}
            </time>
          </div>
        </div>
      </header>
      
      <div className="prose prose-lg prose-gray max-w-none">
        <p className="text-xl text-gray-600 mb-8">{post.excerpt}</p>
        <div dangerouslySetInnerHTML={{ __html: post.content }} />
      </div>
    </article>
  );
}

export async function generateMetadata({
  params
}: {
  params: { slug: string }
}) {
  const post = await getBlogPost(params.slug);

  if (!post) {
    return {
      title: 'Blog Post Not Found'
    };
  }

  return {
    title: post.title,
    description: post.excerpt,
    authors: [{ name: post.author }],
    keywords: post.tags,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
      type: 'article',
      publishedTime: post.publishedAt.toISOString(),
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  };
}

Advanced Dynamic Pages with API Integration

Real-world dynamic pages Next.js applications often need to fetch data from external APIs or databases. Here’s how to implement robust data fetching:

// lib/api.ts
const API_BASE_URL = process.env.API_BASE_URL || 'https://jsonplaceholder.typicode.com';

export interface ApiProduct {
  id: number;
  title: string;
  price: number;
  description: string;
  category: string;
  image: string;
  rating: {
    rate: number;
    count: number;
  };
}

export async function fetchProduct(id: string): Promise<ApiProduct | null> {
  try {
    const response = await fetch(`${API_BASE_URL}/products/${id}`, {
      next: { revalidate: 3600 } // Revalidate every hour
    });

    if (!response.ok) {
      if (response.status === 404) {
        return null;
      }
      throw new Error(`Failed to fetch product: ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    console.error('Error fetching product:', error);
    return null;
  }
}

export async function fetchProducts(): Promise<ApiProduct[]> {
  try {
    const response = await fetch(`${API_BASE_URL}/products`, {
      next: { revalidate: 1800 } // Revalidate every 30 minutes
    });

    if (!response.ok) {
      throw new Error(`Failed to fetch products: ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    console.error('Error fetching products:', error);
    return [];
  }
}

// app/shop/[productId]/page.tsx
import { fetchProduct, fetchProducts } from '@/lib/api';
import { Suspense } from 'react';

function ProductSkeleton() {
  return (
    <div className="container mx-auto px-4 py-8">
      <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
        <div className="aspect-square bg-gray-200 rounded-lg animate-pulse" />
        <div className="space-y-4">
          <div className="h-8 bg-gray-200 rounded animate-pulse" />
          <div className="h-4 bg-gray-200 rounded animate-pulse" />
          <div className="h-4 bg-gray-200 rounded animate-pulse w-2/3" />
          <div className="h-12 bg-gray-200 rounded animate-pulse" />
        </div>
      </div>
    </div>
  );
}

export async function generateStaticParams() {
  const products = await fetchProducts();
  
  return products.slice(0, 10).map((product) => ({
    productId: product.id.toString(),
  }));
}

export default async function ShopProductPage({
  params
}: {
  params: { productId: string }
}) {
  const product = await fetchProduct(params.productId);

  if (!product) {
    notFound();
  }

  return (
    <div className="container mx-auto px-4 py-8">
      <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
        <div className="aspect-square relative">
          <img
            src={product.image}
            alt={product.title}
            className="w-full h-full object-cover rounded-lg"
          />
        </div>
        
        <div className="space-y-6">
          <div>
            <span className="text-sm text-blue-600 font-medium uppercase tracking-wide">
              {product.category}
            </span>
            <h1 className="text-3xl font-bold text-gray-900 mt-2">
              {product.title}
            </h1>
          </div>
          
          <div className="flex items-center space-x-4">
            <div className="flex items-center">
              {[...Array(5)].map((_, i) => (
                <svg
                  key={i}
                  className={`w-5 h-5 ${
                    i < Math.floor(product.rating.rate)
                      ? 'text-yellow-400'
                      : 'text-gray-300'
                  }`}
                  fill="currentColor"
                  viewBox="0 0 20 20"
                >
                  <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                </svg>
              ))}
              <span className="text-gray-600 ml-2">
                ({product.rating.count} reviews)
              </span>
            </div>
          </div>
          
          <p className="text-gray-600 leading-relaxed">
            {product.description}
          </p>
          
          <div className="border-t pt-6">
            <div className="flex items-center justify-between mb-4">
              <span className="text-3xl font-bold text-gray-900">
                ${product.price.toFixed(2)}
              </span>
            </div>
            
            <button className="w-full bg-blue-600 text-white py-3 px-6 rounded-lg hover:bg-blue-700 transition-colors font-medium">
              Add to Cart
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

export async function generateMetadata({
  params
}: {
  params: { productId: string }
}) {
  const product = await fetchProduct(params.productId);

  if (!product) {
    return {
      title: 'Product Not Found',
    };
  }

  return {
    title: `${product.title} - Shop`,
    description: product.description,
    keywords: [product.category, 'shop', 'buy online'],
    openGraph: {
      title: product.title,
      description: product.description,
      images: [product.image],
      type: 'product',
    },
    twitter: {
      card: 'summary_large_image',
      title: product.title,
      description: product.description,
      images: [product.image],
    },
  };
}

Error Handling and Loading States

Robust dynamic pages Next.js applications require proper error handling and user feedback:

// app/blog/[slug]/loading.tsx
export default function Loading() {
  return (
    <div className="container mx-auto px-4 py-8 max-w-4xl">
      <div className="animate-pulse">
        <div className="aspect-video bg-gray-200 rounded-lg mb-6" />
        <div className="space-y-4">
          <div className="flex space-x-2">
            <div className="h-6 bg-gray-200 rounded w-16" />
            <div className="h-6 bg-gray-200 rounded w-20" />
          </div>
          <div className="h-12 bg-gray-200 rounded" />
          <div className="h-4 bg-gray-200 rounded w-1/3" />
          <div className="space-y-2">
            <div className="h-4 bg-gray-200 rounded" />
            <div className="h-4 bg-gray-200 rounded" />
            <div className="h-4 bg-gray-200 rounded w-5/6" />
          </div>
        </div>
      </div>
    </div>
  );
}

// app/blog/[slug]/error.tsx
'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div className="container mx-auto px-4 py-8 max-w-2xl text-center">
      <div className="space-y-4">
        <h2 className="text-2xl font-bold text-gray-900">
          Something went wrong!
        </h2>
        <p className="text-gray-600">
          We encountered an error while loading this blog post.
        </p>
        <button
          onClick={reset}
          className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors"
        >
          Try again
        </button>
      </div>
    </div>
  );
}

// app/blog/[slug]/not-found.tsx
export default function NotFound() {
  return (
    <div className="container mx-auto px-4 py-8 max-w-2xl text-center">
      <div className="space-y-4">
        <h2 className="text-2xl font-bold text-gray-900">
          Blog Post Not Found
        </h2>
        <p className="text-gray-600">
          The blog post you're looking for doesn't exist or has been moved.
        </p>
        <a
          href="/blog"
          className="inline-block bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors"
        >
          Browse All Posts
        </a>
      </div>
    </div>
  );
}

Performance Optimization for Dynamic Pages

Optimizing dynamic pages Next.js applications requires attention to data fetching, caching, and rendering strategies:

Implementing Incremental Static Regeneration (ISR)

// app/products/[id]/page.tsx with ISR
export const revalidate = 3600; // Revalidate every hour

export default async function ProductPage({
  params
}: {
  params: { id: string }
}) {
  const product = await fetchProductWithCache(params.id);
  
  if (!product) {
    notFound();
  }

  return (
    <div className="container mx-auto px-4 py-8">
      <div className="mb-4 text-sm text-gray-500">
        Last updated: {new Date().toLocaleString()}
      </div>
      {/* Product content */}
    </div>
  );
}

async function fetchProductWithCache(id: string) {
  const response = await fetch(`${API_BASE_URL}/products/${id}`, {
    next: { 
      revalidate: 3600,
      tags: [`product-${id}`] 
    }
  });
  
  if (!response.ok) return null;
  return response.json();
}

SEO Optimization for Dynamic Pages

Proper SEO implementation is crucial for dynamic pages Next.js applications:

// Enhanced metadata generation
export async function generateMetadata({
  params
}: {
  params: { slug: string }
}) {
  const post = await getBlogPost(params.slug);

  if (!post) {
    return {
      title: 'Page Not Found',
    };
  }

  const canonicalUrl = `https://yoursite.com/blog/${params.slug}`;

  return {
    title: post.title,
    description: post.excerpt,
    keywords: post.tags.join(', '),
    authors: [{ name: post.author }],
    creator: post.author,
    publisher: 'Your Site Name',
    alternates: {
      canonical: canonicalUrl,
    },
    openGraph: {
      title: post.title,
      description: post.excerpt,
      url: canonicalUrl,
      siteName: 'Your Site Name',
      images: [
        {
          url: post.coverImage,
          width: 1200,
          height: 630,
          alt: post.title,
        },
      ],
      locale: 'en_US',
      type: 'article',
      publishedTime: post.publishedAt.toISOString(),
      authors: [post.author],
      tags: post.tags,
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      creator: '@yourhandle',
      images: [post.coverImage],
    },
    robots: {
      index: true,
      follow: true,
      googleBot: {
        index: true,
        follow: true,
        'max-video-preview': -1,
        'max-image-preview': 'large',
        'max-snippet': -1,
      },
    },
  };
}

Conclusion

Creating effective dynamic pages Next.js applications requires understanding the framework’s routing system, data fetching strategies, and performance optimization techniques. This comprehensive guide has covered everything from basic dynamic routing to advanced features like ISR, proper error handling, and SEO optimization.

The key to successful dynamic pages Next.js implementation lies in choosing the right rendering method for your use case, implementing proper error boundaries, and optimizing for both performance and user experience. Whether you’re building a blog, e-commerce site, or complex web application, these patterns and best practices will help you create scalable, maintainable dynamic pages that perform well and provide excellent user experiences.

Remember to always consider your specific use case when implementing dynamic pages Next.js solutions. Static generation works best for content that doesn’t change frequently, while server-side rendering is ideal for personalized or real-time content. By leveraging Next.js’s powerful features thoughtfully, you can build dynamic pages that are both performant and user-friendly.

Comments (0)

Comment


Note: All Input Fields are required.