CMS Clash: Why Payload Might Just Be Better Than WordPress

Discover why the up-and-coming Payload, with over 22,000 GitHub stars, is challenging the established WordPress.

August 13, 2024

By Carson Gron

Payload Overview

Welcome to our deep dive into Payload, a cutting-edge open source Content Management System designed for developers who crave flexibility, performance, and an exceptional development experience. If you’ve been working with traditional CMS platforms like WordPress, you might find Payload’s headless architecture and modern tooling a breath of fresh air. In this blog post, we'll explore what makes Payload stand out, especially its v3 Beta features, and compare it to WordPress to help you decide which CMS is right for your next project.

What is Payload?

Payload is a headless CMS, meaning it decouples content management from content delivery. Instead of serving both the backend and frontend like traditional CMSs, Payload focuses on managing and delivering content via APIs, leaving the frontend to be built with any technology you choose. This approach provides unparalleled flexibility, allowing developers to create highly performant and scalable applications.

Key Features of Payload

  • TypeScript First: Payload is built entirely with TypeScript, providing type safety and a better development experience.
  • Customizable Admin Panel: The admin panel is built with React and can be fully customized to fit your needs.
  • Flexible Schemas: Define content structures with powerful and flexible schemas.
  • GraphQL and REST APIs: Out-of-the-box support for both GraphQL and REST APIs for seamless content delivery.
  • Access Control: Fine-grained access control for secure content management.
  • Local File Storage and Cloud Integrations: Supports both local file storage and integrations with cloud storage providers.
  • Plugins and Extensions: Easily extend functionality with a variety of plugins and extensions.
  • Self-hosted: Maintain full control over your data by self-hosting Payload.

Flexibility and Power in a Headless Setup With Next.js

One of the standout features of Payload is its seamless integration with modern frontend frameworks like Next.js. This headless setup provides several benefits:

  • Static Site Generation (SSG): Generate static pages at build time, resulting in blazing-fast load times and improved SEO.
  • Server-Side Rendering (SSR): Render pages on the server at request time for dynamic content delivery.
  • API Routes: Create custom API routes within your Next.js project for efficient data fetching and processing.

With Payload managing your content and Next.js handling the frontend, you get a modular and scalable architecture that excels in performance and developer experience.

What’s New in v3 Beta?

Late in 2023, Payload set out to achieve an ambitious goal: building Payload on Next.js. This dream turned to a reality not that the v3 beta is available. The big takeaway? You can now install the entirety of Payload into any Next.js app. That’s right, an entire CMS thats powerful, intuitive, and ready to customize with a simple one-line command: npx create-payload-app@beta .

This setup results in a seamless combination where Payload CMS's admin panel and APIs are directly within your Next.js application, reducing technical complexity and improving engineering quality.

Other highlights of the v3 beta include:

  • Turbopack Support: Works out of the box.
  • Fully-ESM: Payload is now fully ECMAScript Modules across the board.
  • Deploy to Vercel: You can now deploy Payload to Vercel easily.
  • Server-side HMR: Server-side Hot Module Replacement works out of the box.
  • Server Components: All custom React components can be server components by default.
  • Express Support: Can still be used with Next.js' Custom Server functionality.

Head to Head Comparison With WordPress

Payload and WordPress are two popular Content Management Systems (CMS), each with its own strengths and weaknesses. This comparison will delve into their architecture, language, admin panel, content delivery methods, hosting options, plugins and extensions, performance, security, developer experience, and use cases. Understanding these aspects will help you decide which CMS is the best fit for your project.

TL;DR - Here’s a Quick Overview

FeaturePayloadWordPress
ArchitectureHeadless CMS, API-firstMonolithic CMS
LanguageTypeScriptPHP
Admin PanelReact-based, customizablePHP-based, customizable through themes
Content DeliveryAPI-driven (REST and GraphQL)Primarily server-rendered, API optional
HostingSelf-hosted or Payload CloudSelf-hosted or WordPress.com
Plugins and ExtensionRich plugin system, developer-focusedExtensive plugin ecosystem
PerformanceHigh performance, optimized for headlessOften slow without extensive optimization
Developer ExperienceType-safe, modern toolingPHP-based, extensive documentation
Use CasesIdeal for website, custom applications, APIs, Digital Asset Managers, etc.Broad website use, from simple blogs to complex sites

Architecture

Payload:

  • Headless CMS, API-first: Payload follows a headless architecture, which means it decouples the backend content management from the frontend presentation layer. This API-first approach allows developers to use any frontend technology (e.g., React, Vue.js, Next.js) to build their website or application, providing greater flexibility and control over the user experience.

WordPress:

  • Monolithic CMS: WordPress is a traditional monolithic CMS, where the backend and frontend are tightly integrated. This architecture can make customization more challenging and may result in slower performance, especially for large or complex sites.
  • Headless Setup: While WordPress can be used in a headless configuration, it comes with several disadvantages and challenges such as: Heavy performance overhead, Complexity with maintaining and deploying two systems in sync, and development challenges as knowledge of both WordPress/PHP and the chosen frontend framework is required.

Language

Payload:

  • TypeScript: Payload is built entirely with TypeScript, a statically typed superset of JavaScript. This ensures type safety, better code readability, and fewer runtime errors, contributing to a more robust and maintainable codebase.

WordPress:

  • PHP: WordPress is written in PHP, a widely-used server-side scripting language. While PHP has a large community and extensive documentation, it is considered less modern compared to TypeScript, and maintaining large codebases can be more challenging.

Admin Panel

Payload:

  • React-based, customizable: The admin panel of Payload is built with React, offering a modern, responsive, and highly customizable interface. Developers can easily extend and tailor the admin panel to meet specific project requirements.

WordPress:

  • PHP-based, customizable through themes: WordPress's admin panel is built with PHP and can be customized using themes and plugins. While it offers extensive customization options, it may not be as flexible or modern as a React-based interface.

Content Delivery

Payload:

  • API-driven (REST and GraphQL): Payload delivers content via REST and GraphQL APIs, enabling efficient and flexible content delivery. This approach is well-suited for headless setups, where the frontend and backend are separate.

WordPress:

  • Primarily server-rendered, API optional: WordPress traditionally serves content through server-rendered PHP templates. While it does offer REST and GraphQL APIs, these are often used as an afterthought rather than being a core part of the architecture.

Hosting

Payload:

  • Self-hosted: Payload is designed to be self-hosted, giving developers full control over the hosting environment, performance optimizations, and security configurations.
  • Vercel: With the v3 beta, Payload can be easily deployed to Vercel, leveraging its serverless infrastructure and seamless integration with Next.js for efficient and scalable hosting.
  • Coolify: Another excellent option for self-hosting Payload is Coolify, an open-source platform that simplifies deploying, managing, and scaling applications. It offers a user-friendly interface and powerful features to help developers manage their Payload instances with ease.
  • Payload Cloud: Ideal for hassle-free cloud hosting: Payload Cloud provides a managed hosting solution designed specifically for Payload. It eliminates the complexities of self-hosting by offering a fully optimized, secure, and scalable environment. With Payload Cloud, developers can focus on building applications without worrying about server maintenance, performance tuning, or security updates. This makes it an excellent choice for teams seeking a reliable and effortless deployment experience while leveraging the full power of Payload.

WordPress:

  • Self-hosted or WordPress.com: WordPress can be self-hosted or hosted on WordPress.com, a managed hosting service. This provides flexibility in hosting options, but managed hosting can come with limitations in customization and performance optimization.

Plugins and Extensions

Payload:

  • Rich plugin system, developer-focused: Payload offers a rich plugin system focused on developers. Plugins and extensions can easily be built using modern JavaScript and TypeScript, allowing for greater flexibility and integration with other tools and services and quicker development for custom plugins and extensions.

WordPress:

  • Extensive plugin ecosystem: WordPress boasts an extensive plugin ecosystem with thousands of plugins available for various functionalities. However, the quality of plugins can vary, and using too many plugins can negatively impact performance and security.

Developer Experience

Payload:

  • Type-safe, modern tooling: With TypeScript, React, and Node.js, Payload offers a modern development experience. Type safety, modularity, and powerful developer tools contribute to efficient and enjoyable development workflows.

WordPress:

  • PHP-based, extensive documentation: WordPress has a mature and well-documented development environment based on PHP. However, it may feel dated compared to modern JavaScript frameworks, and managing large projects can be challenging.

Use Cases

Payload:

  • Ideal for custom applications, APIs: Ideal for custom applications, APIs, and more: Payload is not just a CMS; it serves as a robust application framework capable of handling a variety of use cases. Its flexibility and performance make it ideal for building custom applications, APIs, digital asset managers, and more. Whether you need a headless CMS for a complex, interactive web application or a versatile platform for managing digital assets, Payload provides the tools and capabilities to meet diverse project requirements.

WordPress:

  • Broad use, from blogs to complex sites: WordPress is versatile and widely used for various types of websites, from simple blogs to complex sites. Its extensive plugin ecosystem and ease of use make it a popular choice for many projects.

Who’s the Winner?

WordPress has long been a staple in the CMS world, but for developers aiming for modernity and efficiency, Payload is the clear winner. With its flexible headless architecture, robust TypeScript foundation, and sleek React-based admin panel, Payload is perfectly equipped to handle the complexities of contemporary web applications.

Make the leap to a more powerful and versatile platform. Discover why Payload is gaining traction among developers and see how it can revolutionize your approach to content management and application development. For those seeking to build award winning websites, next-generation applications, APIs, and digital asset managers, Payload offers the tools and flexibility required to succeed. Don’t settle for outdated solutions—explore Payload today and experience the future of content management.

What Makes Up Payload CMS?

Now that we've compared Payload and WordPress, let's dive deeper into the components that make Payload a powerful and flexible platform. We'll explore creating a new project and the code base structure, the Payload configuration file, and provide examples of creating our own custom collections and blocks.

Project Init and Codebase Structure

To get started with Payload, you need to initialize a new project but let’s talk about a few things first…

Payload by default uses PNPM as a package manager, don’t be alarmed if you’re a NPM fanatic as these steps should work the exact same. Also, I’m on a Mac but these steps should still be compatible with any Windows or Linux system.

We’re going to be creating a new project with Payload, Next.js, Tailwind CSS, and Postgres for our database, ensure that you have a database available to connect to the CMS - I use a local dev database via docker-compose. We’re also going to be using Node Version v18.20.4.

  1. You can initialize a new project using the command line interface and following the instructions: npx create-payload-app@beta (Side note, we’re going to use the website template to quickly get started.
    Developer browsing command options in a dark-themed code editor.
  2. OPTIONAL - Setup a local development postgres database with docker-compose. This will create a postgres-data directory within your project. You will also need to add POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB to your environment variables within .env and then run docker-compose up -d to start up the database
services:
  db:
    image: postgres
    restart: always
    environment:
      # Set the PostgreSQL user
      POSTGRES_USER: ${POSTGRES_USER}
      # Set the PostgreSQL password
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      # Set the PostgreSQL database name
      POSTGRES_DB: ${POSTGRES_DB}

volumes: - ./postgres-data:/var/lib/postgresql/data ports: - '5432:5432'

volumes: postgres-data: driver: local

  1. Update the DATABSE_URI connection string within .env , and start your dev server. You can navigate to localhost:3000 to see the frontend of the site, and localhost:3000/admin to access the Payload admin panel, the first time that you log in you will be prompted to create your first CMS user
  2. Developer navigating between different modes in a code editor.

Now that we’re up and running, let’s take a look at some of the files that make up our codebase. The website template comes set with a bunch of awesome utilities and components for both the Payload admin panel, and our Next.js frontend. The important sections are:

  • src/app/(frontend) - This is our Next.js application that defines our frontend
  • src/app/(payload) - This is the Payload admin interface, we won’t touch this much unless we want to customize/override any OOTB features/ui/etc.
  • src/payload - This is our CMS instance…where all the magic happens! Here we will be defining our sites components such as: Globals, Collections, and Blocks.

    File explorer view showing the structure of a codebase.

Payload Configuration File

The payload.config.ts file is the cornerstone of your Payload CMS setup, defining the overall settings, including collections, globals, blocks, and more. This file is highly modular, allowing you to import and organize different aspects of your CMS efficiently. Below is an in-depth example and explanation of a comprehensive Payload CMS configuration:

Key Components and Features:

  • Admin Customization: The admin section configures components like BeforeLogin and BeforeDashboard to customize the admin interface. It also sets up user management and live preview breakpoints.
  • Editor Configuration: Using lexicalEditor, you can define rich text editor features such as bold, italic, underline, and link functionalities, with custom fields for internal and external links.
  • Database Adapter: The db section configures the database adapter, in this case, postgresAdapter, to connect to a PostgreSQL database using environment variables for the connection string.
  • Collections: The collections array includes different content types like Pages, Posts, Media, Categories, and Users. Each collection is imported and configured separately.
  • CORS and CSRF: These settings configure cross-origin resource sharing (CORS) and cross-site request forgery (CSRF) protection, ensuring secure interactions with the API.
  • Endpoints: Custom endpoints like the seed endpoint allow for additional API functionality, such as populating the database with example data.
  • Globals: The globals array includes global settings like Header and Footer, which are accessible site-wide.
  • Plugins: Payload plugins extend functionality, including:

    • Redirects Plugin: Manages URL redirects with custom fields and hooks.
    • Nested Docs Plugin: Enables nested documents within specified collections.
    • SEO Plugin: Automatically generates SEO-friendly titles and URLs.
    • Form Builder Plugin: Adds form-building capabilities with custom field configurations.
    • Payload Cloud Plugin: Integrates with Payload Cloud for managed hosting.

  • Secret Management: The secret key ensures that sensitive information is securely managed using environment variables.

  • Image Processing: The sharp library is included for efficient image processing.
  • TypeScript Configuration: The typescript section specifies the output file for generated TypeScript types, ensuring type safety throughout the project.

This in-depth configuration example demonstrates the flexibility and power of Payload CMS, allowing you to customize and extend your CMS to meet specific project needs.

//payload.config.ts

// storage-adapter-import-placeholder import { postgresAdapter } from "@payloadcms/db-postgres";

// Importing Payload plugins import { payloadCloudPlugin } from "@payloadcms/plugin-cloud"; import { formBuilderPlugin } from "@payloadcms/plugin-form-builder"; import { nestedDocsPlugin } from "@payloadcms/plugin-nested-docs"; import { redirectsPlugin } from "@payloadcms/plugin-redirects"; import { seoPlugin } from "@payloadcms/plugin-seo"; import { BoldFeature, FixedToolbarFeature, HeadingFeature, ItalicFeature, LinkFeature, lexicalEditor, } from "@payloadcms/richtext-lexical"; import sharp from "sharp"; // editor-import import { UnderlineFeature } from "@payloadcms/richtext-lexical"; import path from "path"; import { buildConfig } from "payload"; import { fileURLToPath } from "url";

// Importing collections, globals, and components import Categories from "./payload/collections/Categories"; import { Media } from "./payload/collections/Media"; import { Pages } from "./payload/collections/Pages"; import { Posts } from "./payload/collections/Posts"; import Users from "./payload/collections/Users"; import BeforeDashboard from "./payload/components/BeforeDashboard"; import BeforeLogin from "./payload/components/BeforeLogin"; import { seed } from "./payload/endpoints/seed"; import { Footer } from "./payload/globals/Footer/Footer"; import { Header } from "./payload/globals/Header/Header"; import { revalidateRedirects } from "./payload/hooks/revalidateRedirects"; import { GenerateTitle, GenerateURL } from "@payloadcms/plugin-seo/types"; import { Page, Post } from "src/payload-types";

// Setting up file paths const filename = fileURLToPath(import.meta.url); const dirname = path.dirname(filename);

// SEO plugin functions const generateTitle: GenerateTitle<Post | Page> = ({ doc }) => { return doc?.title ? </span><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst">${doc.title}</span></span></span><span class="hljs-string"> | Payload Website Template : "Payload Website Template"; };

const generateURL: GenerateURL<Post | Page> = ({ doc }) => { return doc?.slug ? </span><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst">${process.env.NEXT_PUBLIC_SERVER_URL}</span></span></span><span class="hljs-string">/</span><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst">${doc.slug}</span></span></span><span class="hljs-string"> : process.env.NEXT_PUBLIC_SERVER_URL; };

export default buildConfig({ admin: { components: { // The BeforeLogin component renders a message that you see while logging into your admin panel. // Feel free to delete this at any time. Simply remove the line below and the import BeforeLogin statement on line 15. beforeLogin: [BeforeLogin], // The BeforeDashboard component renders the 'welcome' block that you see after logging into your admin panel. // Feel free to delete this at any time. Simply remove the line below and the import BeforeDashboard statement on line 15. beforeDashboard: [BeforeDashboard], }, user: Users.slug, livePreview: { breakpoints: [ { label: "Mobile", name: "mobile", width: 375, height: 667, }, { label: "Tablet", name: "tablet", width: 768, height: 1024, }, { label: "Desktop", name: "desktop", width: 1440, height: 900, }, ], }, }, // This config helps us configure global or default features that the other editors can inherit editor: lexicalEditor({ features: () => { return [ UnderlineFeature(), BoldFeature(), ItalicFeature(), LinkFeature({ enabledCollections: ["pages", "posts"], fields: ({ defaultFields }) => { const defaultFieldsWithoutUrl = defaultFields.filter((field) => { if ("name" in field && field.name === "url") return false; return true; });

        <span class="hljs-keyword">return</span> [
          ...defaultFieldsWithoutUrl,
          {
            <span class="hljs-attr">name</span>: <span class="hljs-string">"url"</span>,
            <span class="hljs-attr"><span class="hljs-keyword">type</span></span>: <span class="hljs-string">"text"</span>,
            <span class="hljs-attr">admin</span>: {
              <span class="hljs-attr">condition</span>: <span class="hljs-function"><span class="hljs-function">(</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">{ linkType }</span></span></span><span class="hljs-function">) =&gt;</span></span> linkType !== <span class="hljs-string">"internal"</span>,
            },
            <span class="hljs-attr">label</span>: <span class="hljs-function"><span class="hljs-function">(</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">{ t }</span></span></span><span class="hljs-function">) =&gt;</span></span> t(<span class="hljs-string">"fields:enterURL"</span>),
            <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
          },
        ];
      },
    }),
  ];
},

}), db: postgresAdapter({ pool: { connectionString: process.env.DATABASE_URI || "", }, }), collections: [Pages, Posts, Media, Categories, Users], cors: [process.env.PAYLOAD_PUBLIC_SERVER_URL || ""].filter(Boolean), csrf: [process.env.PAYLOAD_PUBLIC_SERVER_URL || ""].filter(Boolean), endpoints: [ // The seed endpoint is used to populate the database with some example data // You should delete this endpoint before deploying your site to production { handler: seed, method: "get", path: "/seed", }, ], globals: [Header, Footer], plugins: [ redirectsPlugin({ collections: ["pages", "posts"], overrides: { // @ts-expect-error fields: ({ defaultFields }) => { return defaultFields.map((field) => { if ("name" in field && field.name === "from") { return { ...field, admin: { description: "You will need to rebuild the website when changing this field.", }, }; } return field; }); }, hooks: { afterChange: [revalidateRedirects], }, }, }), nestedDocsPlugin({ collections: ["categories"], }), seoPlugin({ generateTitle, generateURL, }), formBuilderPlugin({ fields: { payment: false, }, formOverrides: { fields: ({ defaultFields }) => { return defaultFields.map((field) => { if ("name" in field && field.name === "confirmationMessage") { return { ...field, editor: lexicalEditor({ features: ({ rootFeatures }) => { return [ ...rootFeatures, FixedToolbarFeature(), HeadingFeature({ enabledHeadingSizes: ["h1", "h2", "h3", "h4"], }), ]; }, }), }; } return field; }); }, }, }), payloadCloudPlugin(), // storage-adapter-placeholder ], secret: process.env.PAYLOAD_SECRET, sharp, typescript: { outputFile: path.resolve(dirname, "payload-types.ts"), }, });

Blocks, Collections, and Globals - What Are They?

In Payload, collections, blocks, and globals are fundamental components that enable you to structure, manage, and display your content efficiently. Let's look at each of these components to understand what they are, and how they work together to help define your website.

Collections

Collections in Payload define the types of content you want to manage. Each collection represents a content model with customizable fields and relationships, providing a powerful way to structure your data.

Example: Media Collection

This Media collection is used to manage and store various media files like images, videos, and documents:

import { CollectionConfig } from 'payload/types';

const Media: CollectionConfig = { slug: 'media', // Unique identifier for the collection labels: { singular: 'Media', // Singular label for the admin panel plural: 'Media', // Plural label for the admin panel }, upload: { staticURL: '/media', // URL path where media files are served staticDir: 'media', // Directory where media files are stored imageSizes: [ { name: 'thumbnail', // Name of the image size width: 300, // Width in pixels height: 300, // Height in pixels }, { name: 'medium', width: 800, height: 600, }, ], adminThumbnail: 'thumbnail', // Thumbnail size used in the admin panel }, fields: [ { name: 'altText', // Field name type: 'text', // Field type label: 'Alt Text', // Label in the admin panel required: true, // Field is required }, { name: 'caption', type: 'textarea', label: 'Caption', }, ], };

export default Media;

Blocks

Blocks in Payload are modular, reusable content components that can be embedded within other fields. They enable you to create flexible and repeatable content structures.

Example: Accordion Block

The Accordion block is a reusable content structure that allows you to create collapsible sections within your content. Here’s a super simple example of an accordion:

import { Block } from 'payload/types';

const Accordion: Block = { slug: 'accordion', // Unique identifier for the block labels: { singular: 'Accordion', // Singular label for the admin panel plural: 'Accordions', // Plural label for the admin panel }, fields: [ { name: 'title', // Field name type: 'text', // Field type label: 'Title', // Label in the admin panel required: true, // Field is required }, { name: 'items', // Field name type: 'array', // Field type for a list of items label: 'Items', // Label in the admin panel fields: [ { name: 'label', // Field name type: 'text', // Field type label: 'Label', // Label in the admin panel required: true, // Field is required }, { name: 'content', type: 'richText', label: 'Content', required: true, }, ], }, ], };

export default Accordion;

Globals

Globals in Payload are used to define site-wide settings or content that needs to be accessed globally across the site. This modularity ensures that global settings can be easily managed and updated.

Example: Site Settings Global

Site settings are a common use case for globals, providing a central place to manage settings like site metadata, contact information, or global content sections like headers and footers.

import { GlobalConfig } from 'payload/types';

const SiteSettings: GlobalConfig = { slug: 'site-settings', // Unique identifier for the global configuration label: 'Site Settings', // Human-readable name for the global configuration fields: [ { name: 'siteName', // Field name type: 'text', // Field type label: 'Site Name', // Label in the admin panel required: true, // Field is required }, { name: 'description', type: 'textarea', label: 'Description', }, { name: 'logo', type: 'upload', relationTo: 'media', // Reference to the Media collection for file uploads label: 'Site Logo', }, ], };

export default SiteSettings;

How Do They Work Together?

Payload components are defined in the src/payload/* directory, and they map to the frontend components in src/app/blocks and src/app/components. Here’s how they work together:

  1. Collections: Define the primary data models for your application, such as posts, media, and users. These are stored in src/payload/collections. Each collection can be accessed via the Payload API for CRUD operations. For example, a Posts collection might be used to store blog posts.
  2. Blocks: Provide reusable content structures that can be embedded within collections or other blocks. These are defined in src/payload/blocks and used in the frontend components. For instance, an Accordion block might be used within a page layout to create collapsible content sections.
  3. Globals: Offer a centralized place for site-wide settings and configurations, ensuring consistency and ease of management across the entire application. Globals are defined in src/payload/globals. For example, global settings like Header and Footer can be used across multiple pages for consistent styling and content.

Mapping Payload Data to Frontend Components

To connect Payload CMS data with your frontend, you can use the payload.find method to fetch data and render it within your React components.

For example our website template uses this method to fetch posts within src/app/(frontend)/posts/page/[pageNumber]/page.tsx

import type { Metadata } from 'next/types'

import { CollectionArchive } from '@/components/CollectionArchive' import { PageRange } from '@/components/PageRange' import { Pagination } from '@/components/Pagination' import configPromise from '@payload-config' import { getPayloadHMR } from '@payloadcms/next/utilities' import React from 'react'

export const dynamic = 'force-static' export const revalidate = 600

export default async function Page({ params: { pageNumber = 2 } }) { const payload = await getPayloadHMR({ config: configPromise })

const posts = await payload.find({ collection: 'posts', depth: 1, limit: 12, page: pageNumber, })

return ( <div className="pt-24 pb-24"> <div className="container mb-16"> <div className="prose dark:prose-invert max-w-none"> <h1>Posts</h1> </div> </div>

  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=</span></span><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string">"container mb-8"</span></span></span></span><span class="xml"><span class="hljs-tag">&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PageRange</span>
      <span class="hljs-attr">collection</span>=</span></span><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string">"posts"</span></span></span></span><span class="xml"><span class="hljs-tag">
      <span class="hljs-attr">currentPage</span>=<span class="hljs-string">{posts.page}</span>
      <span class="hljs-attr">limit</span>=<span class="hljs-string">{</span></span></span><span class="hljs-number"><span class="xml"><span class="hljs-tag"><span class="hljs-string">12</span></span></span></span><span class="xml"><span class="hljs-tag"><span class="hljs-string">}</span>
      <span class="hljs-attr">totalDocs</span>=<span class="hljs-string">{posts.totalDocs}</span>
    /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">CollectionArchive</span> <span class="hljs-attr">posts</span>=<span class="hljs-string">{posts.docs}</span> /&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=</span></span><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string">"container"</span></span></span></span><span class="xml"><span class="hljs-tag">&gt;</span>
    {posts.totalPages &gt; </span><span class="hljs-number"><span class="xml">1</span></span><span class="xml"> &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">Pagination</span> <span class="hljs-attr">page</span>=<span class="hljs-string">{posts.page}</span> <span class="hljs-attr">totalPages</span>=<span class="hljs-string">{posts.totalPages}</span> /&gt;</span>}
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>

) }

export function generateMetadata({ params: { pageNumber = 2 } }): Metadata { return { title: Payload Website Template Posts Page <span class="hljs-subst">${pageNumber}</span>, } }

export async function generateStaticParams() { const payload = await getPayloadHMR({ config: configPromise }) const posts = await payload.find({ collection: 'posts', depth: 0, limit: 10, })

const pages = []

for (let i = 1; i <= posts.totalPages; i++) { pages.push(i) }

return pages }

Hungry for More?

Stay tuned for more blogs where we'll dive deeper into leveraging Payload as a CMS, including deploying to Vercel, using Coolify for self-hosting and deployments, and in-depth tutorials on leveraging Payload with Next.js to create powerful, dynamic modern websites and applications. In the meantime, I highly recommend checking out some of the online resources available, including the Payload repo (trendingwith 22k + stars), and spinning up an instance of your own locally to tinker and explore!

Some other great resources include:



Carson Headshot

Carson Gron

Product Owner

Carson brings 5+ years of experience from a journey spanning multiple startups. His approach to product design and strategy is not only about aesthetics but about functionality and ease of use. He's a believer in the power of technology to simplify life and enhance experiences, and excels in breaking down technical jargon into understandable insights, ensuring products are as functional as they are innovative. Off the clock, he’s a technology/AI enthusiast and sports lover - actively playing hockey and lacrosse, and sometimes you can even find him coaching the next generation.