Web Development

Implementing custom SEO in Payload CMS

By Devlouix|Draft|5 min read
nextjs-payload-hero-img

Introduction: Why SEO Matters

To reach your target audience, your website needs to stand out before users even visit it. Ranking on search engines like Google is often the first—and most critical—step in that process.

Even well-written content can remain invisible without proper optimization. This is where Search Engine Optimization (SEO) comes in.

SEO ensures that:

  • Search engines can understand your content
  • Your pages appear in relevant search results
  • Users are encouraged to click through to your site

In this guide, you’ll learn how to implement SEO into your existing Payload CMS project. We’ll focus specifically on:

  • Structuring SEO metadata
  • Using the official Payload SEO plugin
  • Connecting everything to your Next.js frontend

By the end, your site won’t just exist—it will be optimized to perform.


Step 1: Installing the Payload SEO Plugin

By default, Payload CMS stores content—but it does not automatically structure it for search engines.

To properly support SEO, we need fields such as:

  • Meta Title
  • Meta Description
  • Social Sharing Image (Open Graph)

Install the plugin

typescript
npm install @payloadcms/plugin-seo

Register the plugin

Open your payload.config.ts file:

typescript
import { buildConfig } from 'payload'
import { seoPlugin } from '@payloadcms/plugin-seo'

export default buildConfig({
  // your existing config

  plugins: [
    seoPlugin({
      collections: ['pages'], // Adds SEO fields to this collection
      uploadsCollection: 'media',

      generateTitle: ({ doc }: any) => {
        return doc?.title ? `${doc.title} | My Website` : 'My Website'
      },
    }),
  ],
})

What this does

  • Adds an SEO tab to your Pages collection
  • Automatically creates fields for metadata
  • Standardizes how SEO data is stored

Step 2: Structuring Your Pages Collection for SEO

Your Pages collection is where SEO data will live alongside your content.

If you don’t already have one, create or update it:

typescript
import { CollectionConfig } from 'payload'

export const Pages: CollectionConfig = {
  slug: 'pages',

  admin: {
    useAsTitle: 'title',
  },

  versions: {
    drafts: true,
    maxPerDoc: 10,
  },

  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    },
    {
      name: 'slug',
      type: 'text',
      required: true,
      unique: true,
      index: true,
    },

    // SEO fields are automatically injected by the plugin

    // Your content fields go here
  ],
}

Why this matters

  • Unique slugs ensure clean URLs (/about, /contact)
  • Indexed fields improve query performance
  • SEO data is stored per page, allowing full control

Step 3: Adding Global SEO Defaults

Not every page will have custom SEO data. You need fallbacks.

This is where Globals come in.

Create Site Settings

payload/globals/SiteSettings.ts

typescript
import { GlobalConfig } from 'payload'

export const SiteSettings: GlobalConfig = {
  slug: 'site-settings',

  fields: [
    {
      name: 'siteName',
      type: 'text',
      required: true,
      defaultValue: 'My Website',
    },
    {
      name: 'defaultDescription',
      type: 'textarea',
      label: 'Default SEO Description',
    },
  ],
}

Register it

typescript
import { SiteSettings } from './globals/SiteSettings'

export default buildConfig({
  globals: [SiteSettings],
})

Why this matters

  • Prevents missing metadata
  • Ensures consistent branding
  • Provides fallback values for SEO

Step 4: Generating SEO Metadata in Next.js

Now we move to the frontend.

Next.js allows you to dynamically generate SEO metadata using the generateMetadata function.

Dynamic SEO setup

app/[slug]/page.tsx

typescript
import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { Metadata } from 'next'

export async function generateMetadata({ params }): Promise<Metadata> {
  const { slug } = params
  const payload = await getPayload({ config: configPromise })

  // Fetch page
  const result = await payload.find({
    collection: 'pages',
    where: {
      slug: { equals: slug },
    },
  })

  const page = result.docs[0]

  if (!page) return {}

  return {
    title: page.meta?.title || page.title,
    description: page.meta?.description,

    openGraph: {
      title: page.meta?.title || page.title,
      description: page.meta?.description,
      images: page.meta?.image
        ? [{ url: (page.meta.image as any).url }]
        : [],
    },
  }
}

Step 5: Rendering the Page Content

After setting up SEO, render the page normally:

typescript
export default async function Page({ params }) {
  const { slug } = params
  const payload = await getPayload({ config: configPromise })

  const result = await payload.find({
    collection: 'pages',
    where: {
      slug: { equals: slug },
    },
  })

  const page = result.docs[0]

  if (!page) {
    return <div>Page not found</div>
  }

  return (
    <main>
      <h1>{page.title}</h1>
      {/* Render your content here */}
    </main>
  )
}

Step 6: Best Practices for Enterprise SEO

To make your setup truly production-ready:

1. Always fill SEO fields

Encourage editors to:

  • Write clear meta titles (50–60 characters)
  • Write compelling descriptions (150–160 characters)

2. Use meaningful slugs

Good:

/seo-guide-payload

Bad:

/page-123

3. Add Open Graph images

These improve how your pages appear when shared on social platforms.

4. Use fallback values

Never leave metadata empty—use globals.


Conclusion

You’ve now implemented a complete SEO system in Payload CMS and Next.js.

What you achieved

  • Structured SEO data in your CMS
  • Automated metadata generation
  • Search-engine-friendly frontend rendering
  • Scalable architecture for future growth

Next Steps

To go even further, consider:

  • Adding JSON-LD structured data
  • Implementing sitemaps and robots.txt
  • Using incremental static regeneration (ISR)
  • Auditing with tools like Google Search Console

With this setup, your website is no longer just functional—it’s discoverable, competitive, and built for growth.

Enjoyed this article?

Discussion

Join the Discussion

No comments yet. Start the conversation!