Programming

Building a Sitemap with Payload CMS v3 Beta

Written by Preston Garrison ai
Post hero image

Building a Payload CMS Sitemap

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';
4
5export const dynamic = 'force-dynamic';
6
7let payload;
8
9async function initializePayload() {
10 if (!payload) {
11 payload = await getPayloadHMR({ config: configPromise });
12 }
13}
14
15export async function GET() {
16 await initializePayload(); // Ensure Payload is initialized
17
18 const baseUrl = process.env.PAYLOAD_PUBLIC_SERVER_URL || 'https://defaulturl.com'; // Use PAYLOAD_PUBLIC_SERVER_URL
19
20 try {
21 // Fetch all posts from the Payload CMS
22 const posts = await payload.find({
23 collection: 'posts',
24 // No depth specified
25 });
26
27 const today = new Date().toISOString();
28
29 // Create XML content
30 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 > 0
46 ? posts.docs
47 .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`;
56
57 return new Response(xmlContent.trim(), {
58 headers: {
59 'Content-Type': 'application/xml',
60 // Cache for 1 day, then revalidate
61 '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