Adding a Sitemap to Your Next.js Site

Why You Need a Sitemap

Hey there! If you're building a Next.js site and want search engines to find all your content, you need a sitemap. It's like giving Google a map of your website - it helps them discover and index your pages faster. The best part? Next.js 13+ makes it super easy to create one.

The Quick Way

Next.js has a built-in way to generate sitemaps. Just create a sitemap.ts file in your app directory:

// app/sitemap.ts
import { MetadataRoute } from 'next';

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: 'https://yoursite.com',
      lastModified: new Date(),
      changeFrequency: 'yearly',
      priority: 1,
    },
    {
      url: 'https://yoursite.com/about',
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.8,
    },
    {
      url: 'https://yoursite.com/blog',
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 0.9,
    },
  ];
}

That's it! Next.js will automatically create a /sitemap.xml at the root of your site. But let's make it more interesting...

Making it Dynamic

The real power comes when you generate your sitemap dynamically based on your actual content. Here's how I do it with my blog posts and other dynamic content:

import { MetadataRoute } from 'next';

// Your data sources (could be from files, API, etc.)
interface BlogPost {
  slug: string;
  date: string;
}

async function getBlogPosts(): Promise<BlogPost[]> {
  // Get your blog posts from wherever you store them
  return [
    { slug: 'first-post', date: '2024-01-01' },
    { slug: 'second-post', date: '2024-01-15' },
  ];
}

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://yoursite.com';
  
  // Get your dynamic content
  const posts = await getBlogPosts();
  
  // Start with your static routes
  const routes: MetadataRoute.Sitemap = [
    {
      url: baseUrl,
      lastModified: new Date(),
      changeFrequency: 'yearly',
      priority: 1,
    },
  ];
  
  // Add blog posts to sitemap
  const postRoutes = posts.map(post => ({
    url: `${baseUrl}/blog/${post.slug}`,
    lastModified: new Date(post.date),
    changeFrequency: 'monthly' as const,
    priority: 0.7,
  }));
  
  return [...routes, ...postRoutes];
}

A Real-World Example

Here's how I handle multiple content types in my sitemap:

import { MetadataRoute } from 'next';

// Your content types
interface Page {
  path: string;
  lastMod?: Date;
}

interface BlogPost {
  slug: string;
  date: string;
}

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://yoursite.com';
  
  // Static pages with their update frequency
  const staticPages: Page[] = [
    { path: '', lastMod: new Date() },           // Home
    { path: '/about' },                          // About
    { path: '/blog' },                           // Blog index
    { path: '/projects' },                       // Projects
  ];

  // Get dynamic content
  const posts = await getBlogPosts();
  const products = await getProducts();
  
  const routes: MetadataRoute.Sitemap = [
    // Add static pages
    ...staticPages.map(page => ({
      url: `${baseUrl}${page.path}`,
      lastModified: page.lastMod || new Date(),
      changeFrequency: 'monthly' as const,
      priority: page.path === '' ? 1 : 0.8,
    })),
    
    // Add blog posts
    ...posts.map(post => ({
      url: `${baseUrl}/blog/${post.slug}`,
      lastModified: new Date(post.date),
      changeFrequency: 'monthly' as const,
      priority: 0.7,
    })),
    
    // Add products
    ...products.map(product => ({
      url: `${baseUrl}/products/${product.id}`,
      lastModified: new Date(product.updatedAt),
      changeFrequency: 'weekly' as const,
      priority: 0.9,
    })),
  ];
  
  return routes;
}

Pro Tips

Here are some things I've learned about sitemaps:

  • Set priority based on importance - home page should be 1.0, less important pages lower
  • Use changeFrequency wisely - don't say daily if you rarely update the content
  • Keep your sitemap under 50,000 URLs (that's Google's limit)
  • For bigger sites, create multiple sitemaps and a sitemap index (I'll cover that in another tutorial)
  • Always include the lastModified date - it helps search engines know when to recrawl

Testing Your Sitemap

Once you've set it up, here's how to check if it's working:

  1. Visit yoursite.com/sitemap.xml - it should show your sitemap in XML format
  2. Use the Google Search Console to submit your sitemap
  3. Check for any errors in the Search Console's sitemap report

Common Gotchas

Watch out for these common issues:

  • Always use absolute URLs (https://yoursite.com/page), not relative ones (/page)
  • The changeFrequency and priority fields are optional, but good to include
  • Don't forget to add your sitemap URL to your robots.txt file
  • Keep your dates in ISO format (the Date object handles this automatically)

Adding to robots.txt

Create a robots.txt file in your public folder:

# public/robots.txt
User-agent: *
Allow: /

# Add your sitemap URL
Sitemap: https://yoursite.com/sitemap.xml

Wrapping Up

That's it! You now have a dynamic sitemap that updates automatically with your content. Remember to submit it to search engines through their webmaster tools, and you're good to go! If you're handling lots of dynamic routes or need to split your sitemap into multiple files, check out my other tutorial on advanced sitemap techniques (coming soon).

Quick Tip: If your site has thousands of pages, consider caching the sitemap generation or using incremental static regeneration (ISR) to avoid regenerating it on every request.