Building a Sitemap with Payload CMS v3 Beta
I needed to build a sitemap for Google to index, that included all my blog posts. This source should be easy to modify to add any other page collections you want included, as well as adding your own hard coded pages. The important thing was to put it in a directory that wouldn't be affected by any layout.tsx files, so the api directory was perfect. Make sure you don't have any spaces in front of the xml tags in the source code. This can be accessed by going to /api/sitemap.xml
create a file
1src/app/api/sitemap/route.ts
contents:
1import { NextResponse } from 'next/server';2import { getPayloadHMR } from '@payloadcms/next/utilities';3import configPromise from '@payload-config';45export const dynamic = 'force-dynamic';67let payload;89async function initializePayload() {10 if (!payload) {11 payload = await getPayloadHMR({ config: configPromise });12 }13}1415export async function GET() {16 await initializePayload(); // Ensure Payload is initialized1718 const baseUrl = process.env.PAYLOAD_PUBLIC_SERVER_URL || 'https://defaulturl.com'; // Use PAYLOAD_PUBLIC_SERVER_URL1920 try {21 // Fetch all posts from the Payload CMS22 const posts = await payload.find({23 collection: 'posts',24 // No depth specified25 });2627 const today = new Date().toISOString();2829 // Create XML content30 const xmlContent = `31<?xml version="1.0" encoding="UTF-8" ?>32<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">33 <url>34 <loc>${baseUrl}/</loc>35 <lastmod>${today}</lastmod>36 </url>37 <url>38 <loc>${baseUrl}/posts</loc>39 <lastmod>${today}</lastmod>40 </url>41 <url>42 <loc>${baseUrl}/about</loc>43 <lastmod>${today}</lastmod>44 </url>45 ${posts.docs.length > 046 ? posts.docs47 .map(post => `48 <url>49 <loc>${baseUrl}/posts/${post.slug}</loc>50 <lastmod>${new Date(post.updatedAt).toISOString()}</lastmod>51 </url>`).join('')52 : ''53 }54</urlset>55`;5657 return new Response(xmlContent.trim(), {58 headers: {59 'Content-Type': 'application/xml',60 // Cache for 1 day, then revalidate61 'Cache-Control': 'public, max-age=86400, s-maxage=86400, stale-while-revalidate',62 },63 });64 } catch (error) {65 console.error('Error fetching posts:', error);66 return NextResponse.json({ error: 'Failed to fetch posts' }, { status: 500 });67 }68}69