All Blog Articles

Building a Website with Payload CMS and NextJS: Setup & Dynamic Pages - Part 2

Media
Sandro WegmannMay 1, 2025
Build flexible websites with Payload CMS and NextJS: Step-by-step setup for dynamic content pages and blocks.

Building a Website with Payload CMS and NextJS: Setup & Dynamic Pages


Welcome to the second installment of our series on building a website using Payload CMS and NextJS. In this article, we will dive deep into creating dynamic pages and blocks, while also setting up our project environment. This guide aims to help you understand the process clearly, providing you with all the necessary steps to build your own website. Whether you're a beginner or looking to sharpen your skills, you’ll find valuable insights here.


This article provides the written step-by-step tutorial version of our comprehensive video guide available on YouTube at https://www.youtube.com/watch?v=r9I6pA3WmeE. For visual learners or those who prefer following along with video instruction, the YouTube tutorial covers the same content but offers additional context and real-time demonstrations of each implementation step in the process. Watch the video alongside this guide for the complete learning experience.


Table of Contents


Setting Up the Backend

To kick things off, we need to set up our backend environment. Start by creating an empty repository and opening a terminal session. The first command to run is:

npx create-payload-app

This command will prompt you for a project name. Let's name it payload-next-js-tutorial-backend. Next, select the blank template and choose a MongoDB database since we will be using it to store our data.

payload-cms-installation-terminal-setup.png

After entering your MongoDB connection string from MongoDB Atlas, the installation of all dependencies will commence. Once the dependencies are installed, navigate to the project folder and clean it up by deleting the Dockerfile and Docker Compose files.

Next, we need to change the server port to 4000 since the backend will run on this port, while the front end will run on port 3000. In the Payload configuration file, remove the Payload Cloud plugin, as it’s not necessary for this tutorial. You can then start your development server:

npm run dev

By visiting localhost:4000 in your browser, you will encounter a welcome screen prompting you to create your first user.


Creating Collections

Now that we have our backend set up, we can proceed to create the collections we need. Our first collection will be for Pages, which will allow us to create pages from the backend. Additionally, we will create a second collection called Media to store images and videos.


Setting Up the Pages Collection

To create the pages collection, export a constant called pages. Each collection requires a slug, which we will set to pages. You will also need to add labels for singular and plural forms, which will be Page and Pages, respectively.

Next, we will add three important fields:

  • The name of the page, which is required.
  • The slug, which will appear in the URL. Each page must have a unique slug.
  • The layout, which will serve as the foundation for our dynamic block model.

The layout will be of type blocks and will contain an attribute called blocks, which will be an array (initially empty).

payload-pages-collection-code-setup.png


Setting Up the Media Collection

For the media collection, we will follow a similar approach. Export a constant called media and set its slug to media. It’s crucial to enable uploads for this collection by setting the upload field to true.

We will also add an alt field for SEO purposes. This field will be of type text and is required.


Access Control and Configuration

Both collections must be accessible to the public, meaning we need to set the access attribute to read, which will always return true. This will allow users to retrieve data without logging into Payload.

Next, we need to add these two collections to our Payload configuration. In the configuration file, alongside the users collection, add both the pages and media collections. After saving these changes, restart your development server to see the collections appear in the admin panel.

payload-cms-admin-collections-dashboard.png


Creating Blocks

With our collections set up, it’s time to create blocks that will be used in our pages. In this tutorial, we will create two simple blocks: a Hero Block and a Two Column Block.


Creating the Hero Block

Create a new folder named blocks in the source folder and inside it, create a file called hero.js. This block will require a slug and labels for singular and plural forms.

The Hero Block will consist of three fields:

  • Heading: Centered text.
  • Text: Smaller text below the heading.
  • Background Image: This will allow users to upload an image for the background.


Creating the Two Column Block

Next, create another file for the Two Column Block named two-column.js. This block will also include a heading, text, and an image. However, it will have an additional field called Direction, which will allow users to select the layout of the two columns.

After defining these blocks, import them into your pages layout. Restart the development server to ensure the blocks appear in the admin panel.


Creating Your First Page

Now that we have our blocks ready, let’s create our first page. Navigate to the admin panel, click on Add Page, and fill in the details:

  • Name: Test Page
  • Slug: test

Next, add the Hero Block with a heading and description, and upload a suitable background image. Then, add the Two Column Block with text and an image. Once done, save your page!

payload-cms-create-first-page-interface.png


Setting Up the Frontend with NextJS

With our backend established, let’s set up the NextJS frontend. Create a new terminal and run:

npx create-next-app@latest

Name the project payload-next-js-tutorial-frontend. For simplicity, we will use plain JavaScript and Tailwind CSS for styling. We will not use TypeScript or ESLint for this tutorial.


Installing Axios

Once the dependencies are installed, navigate into your project folder and install Axios for making requests to the backend:

npm install axios


Global Configuration for Axios

To streamline Axios requests, configure a global setting in _app.js. Import Axios and set the base URL using an environment variable. Create a file called .env.local in your root folder and add the following:

NEXT_PUBLIC_BACKEND_URL=http://localhost:4000

Now you can access this variable throughout the project without hardcoding the URL.


Creating Blocks in the Frontend

In the src folder, create a new folder called blocks and create the same blocks as in the backend. Use JSX for the file extension to utilize React's functionality.

For the Hero Block, set it up to receive the heading, text, and background image as props. For the Two Column Block, include the additional direction prop. This will allow us to control the layout of the blocks dynamically.

nextjs-hero-block-component-code.png


Mapping Backend Blocks to Frontend Blocks

Next, create a file named block-list.js that maps the slugs from the backend to the corresponding frontend blocks. This allows NextJS to render the correct components based on the data received from the backend.


Rendering Blocks in Pages

Create a utility function in the utils folder called render-blocks.js. This function will iterate through the layout of each page and render the appropriate blocks based on their types.


Dynamic Routing with NextJS

To implement dynamic routing, create a file in the pages folder named [...slug].js. This file will handle requests for dynamic pages based on their slugs. The component will receive page data as props and utilize the render-blocks function to display the content.

In addition to the component, you will need to export two functions: getStaticPaths and getStaticProps. These functions will fetch the necessary data from the backend based on the slugs.


Testing the Dynamic Pages

After implementing the dynamic routing, restart your frontend server and ensure the backend is running. You can now visit localhost:3000/test to see your newly created page displayed with the data from the backend.


Displaying Images in the Frontend

To display images effectively, return to the Hero Block and utilize the next/image component. This component optimizes images by generating a source set with varying dimensions.

Set the source attribute to the URL of the background image, along with its height and width. Ensure that your NextJS configuration allows images from your backend by defining remote patterns in next.config.js.


Conclusion

Congratulations! You have successfully set up a backend using Payload CMS and created a dynamic frontend with NextJS. This guide has walked you through essential steps, from setting up your environment to creating collections, blocks, and pages. Remember, the key to building a successful website is understanding how each component interacts with one another.

Stay tuned for the next episode in this series, where we will explore global components and data management. If you have any questions or need assistance, feel free to reach out in the comments or via email.


FAQs

What is Payload CMS?

Payload CMS is a headless content management system that allows developers to create customized content structures and manage content efficiently.

Why use NextJS with Payload CMS?

NextJS offers server-side rendering and static site generation capabilities, making it an excellent choice for building performant web applications with Payload CMS.

Can I use TypeScript in this setup?

Yes, while this tutorial uses JavaScript for simplicity, you can easily adapt it to TypeScript by following the appropriate TypeScript conventions.

How do I deploy my Payload CMS and NextJS application?

You can deploy your backend on platforms like DigitalOcean or Heroku and your frontend on Vercel or Netlify for easy deployment of NextJS applications.

Where can I find more resources on Payload CMS and NextJS?

You can explore the official documentation for both Payload CMS and NextJS for more detailed guides and advanced features.