Blogs

Get started building pages with dotCMS, Next.js and Tailwind CSS

Freddy Montes

dotCMS provides you with the perfect API to build all the pages you need for your JamStack site. In this post we are going to show you how to build your pages using Next.js and Tailwind CSS.

The first step is to initialize a new Next.js project:

yarn create next-app project-name

To make sure everything is OK, let's test the project:

cd project-name
yarn dev

Open localhost:3000 and you should see:

Nextjs and dotCMS

Now you have an initialized Nextjs project. When you start the project in dev you have hot-code reloading, error reporting and more.

dotCMS and Next.js

dotCMS provides a series of API calls that you can consume with Next.js, and create pages based on that content. fFor this tutorial we'll be using Page API.

dotCMS Page API

For this tutorial we'll be using the Page API, which gives us all the information of a page created with dotCMS this includes:

  1. The page layout consisting of rows, columns, containers and content.

  2. The content of those pages

The top level object looks like this:

{
    canCreateTemplate: true,
    containers: {},
    layout: {},
    numberContents: 21,
    page: {},
    site: {},
    template: {},
    viewAs: {},
}

To build a page we will focus on three particular properties

{
    containers: {},
    layout: {},
    page: {},
}
  • containers: contains all the information about the content associated with the page and states which container the content belongs to.

  • layout: handles all the information about the page layout, rows, columns. Including their sizes, offsets and a reference to the content inside each column.

  • page: contains the page data such as title, description, SEO etc.

Next.js

Next.js is a framework based on React that allows us to create web applications quickly. It's main feature is that it allows you to create pages generated at build time or pages generated on each request (server side rendered).

The routing system is file based and also has dynamic routes which makes it perfect for integrating with the dotCMS page system.

The process of creating pages

The process will be as follows:

  1. The user enters in the browser: https://yoursite.com/.

  2. Inside Next.js we fetch the main page / to dotCMS using the Page API.

  3. Now in Next.js, with the Page API response we create the React components needed to display the page.

dotCMS NextJs and Tailwind CSS

Since we want to use Tailwind for styling, we start by adding it to our project.

Installing and configuring Tailwind CSS

We install the dependencies:

yarn add tailwindcss@latest postcss@latest autoprefixer@latest

Next, generate your tailwind.config.js and postcss.config.js files:

npx tailwindcss init -p

This will create a tailwind.config.js file with your minimal configuration:

// tailwind.config.js
module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

It will also create a postcss.config.js file that includes tailwindcss and autoprefixer already configured:

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

At the moment this is all we need but if you want more information:

  1. Tailwind CSS configuration documentation

  2. PostCSS preprocesador

Configure Tailwind to remove unused styles in production

Tailwind CSS is a huge library and we have to make sure that we only send the code we are going to use to production.

Open your tailwind.config.js and let's edit the purge line

diff --git a/tailwind.config.js b/tailwind.config.js
index 62dfdaf..d7f0874 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,5 +1,5 @@
 module.exports = {
-  purge: [],
+  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
   darkMode: false, // or 'media' or 'class'
   theme: {
     extend: {},

With this configuration we are telling Tailwind the folders of our pages and components so that it automatically removes all the unused styles when we build for production.

Including Tailwind CSS

Now we need to include Tailwind in our project since we have Tailwind configured correctly.

We edit the /styles/globals.css file, remove everything and just leave these lines:

@tailwind base;
@tailwind components;
@tailwind utilities;

On the same page, we open the pages/index.js file and replace all its content with:

import Head from 'next/head'

export default function Home() {
  return (
    <>
      <Head>
        <title>DotCMS - Next.js & Tailwind CSS</title>
        <meta name="description" content="DotCMS - Next.js & Tailwind CSS" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <h1>DotCMS - Next.js & Tailwind CSS</h1>
    </>
  )
}

With this we have a blank page with Tailwind ready to start development.

Creating our page

To create our first page it is useful to know how the routing system works in Next.js.

Next.js has a file based routing system, all pages are created inside the /pages folder. If you create a /pages/contact.jsx file that automatically creates that route in your https://yourdomain.com/contact application.

Server side render

For this tutorial we are going to generate the page in the request. To do this we export the getServerSideProps function in the /pages/index.js page file.

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

Inside this function we are going to make a request to dotCMS to obtain the object of the page. The result is going to be returned in a props object that Next.js will automatically pass to the Home component.

Getting the page in dotCMS

To make the page request we need an authentication token in dotCMS.

You can get it:

  1. Through an API

  2. Through the UI

The url of the Page API is: https://your.dotcms.com/api/v1/page/json/path/to/page.

export async function getServerSideProps(context) {
  const { containers, layout, page } = await fetch("https://your.dotcms.com/api/v1/page/json/path/to/page",
    {
      headers: {
        Authorization: `Bearer ${AUTH_TOKEN}`,
      },
    }
  )
    .then((res) => res.json())
    .then((data) => data.entity);

  return {
    props: { containers, layout, page }, // will be passed to the page component as props
  };
}

What we did in getServerSideProps.

  1. A fetch request to the URL of the page in dotCMS.

  2. Pass the token in the request header.

  3. Extract containers, layout, page and return them in an object in the props property.

This way we pass the page information to the Home component in order to render the content using components.

Making the layout

First let's understand what information is in the layout object of a dotCMS page.

{
    "layout": {
        "width": null,
        "title": "TITLE",
        "header": true,
        "footer": true,
        "body": {
            "rows": [
                {
                    "columns": [
                        {
                            "containers": [
                                { "identifier": "IDENTIFIER", "uuid": "UUID" }
                            ],
                            "widthPercent": 100,
                            "leftOffset": 1,
                            "styleClass": "someclass",
                            "width": 12,
                        }
                    ],
                }
            ]
        }
    }
}

The grid is made of 12 columns. What we care about when building the page are the following properties:

  • header and footer booleans that indicates show/hide.

  • body includes the rows and columns information with an array ready to iterate and use components.

  • columns each element represents a box.

    • containers is an array of references to the containers containing the column.

    • the widthPercent` the width of the column in percent.

    • leftOffset the position within the grid where this column box starts.

    • styleClass class added by the content creator.

    • width the width of the column box.

With this information we are going to create components with Tailwind classes.

Row component

We create /components/Row.js and add the following code:

export function Row({ children }) {
  return <section className="grid grid-cols-12 gap-2 my-2">{children}</section>
}

This component is going to be used to render each row of the page layout, the grid containers of 12 columns, the vertical margin and the grid gap.

Inside this component we are going to render a series of components, that we are going to create next.

Column component

We create /components/Column.js and add the following code:

export function Column({ children, leftOffset, width }) {
  const end = leftOffset + width;
  return <div className={`col-start-${leftOffset} col-end-${end}`}>{children}</div>;
}

In this case assign where each box of the layout starts and ends in the component. Tailwind provides the col-start-N and col-end-N classes and we have that information in the dotCMS layout object information.

Using and .

To obtain the layout from the props of the Home component we only need the following components:

export default function Home({ layout }) {
  const { rows } = layout.body;

  return (
    <>
      <Head>
        <title>DotCMS - Next.js & Tailwind CSS</title>
        <meta name="description" content="DotCMS - Next.js & Tailwind CSS" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <h1>DotCMS - Next.js & Tailwind CSS</h1>

      <main className="container m-auto">
        {rows.map(({ columns }, i) => (
          <Row key={`row-${i}`}>
            {columns.map(({ leftOffset, width }, k) => (
              <Column leftOffset={leftOffset} width={width} key={`col-${k}`}>
                Column {k}
              </Column>
            ))}
          </Row>
        ))}
      </main>
    </>
  );
}

Depending on the page you are on you should see something like this:

Tailwind and dotCMS

Adding the content

Now that we have the layout, we have to add the content of the page. The content lives in the containers and in the layout object inside columns we have a reference to these containers.

The containers and the content they contain is in the containers property of the page. This object contains detailed information about all the content and containers.

For the purposes of this post we are going to focus on the contentlets property, which is the object for each piece of content that is created and added to the page:

"containers": {
    "ID_OF_CONTAINER": {
        "contentlets": {
            "uuid-N": [
                {
                    "contentType": "ContentTypeName",
                    "field1": "Field 1 Content",
                    "fieldN": "Field N Content"
                }
            ]
        }
    }
}

The most important property for creating components is contentType because based on this property we will be able to create a different React components for each Content Type.

For example if you have a Content Type of type "Event" you are going to create a React Component to render "Event". The rest of the properties are going to be the props of the component.

The component.

For the mapping between the Content Type and it's components we create the component components/Contentlet.js.

import { Activity } from './content-types/Activity';
import { Product } from './content-types/Product';

const Components = {
  Activity,
  Product
}

function Fallback({ title }) {
  return <h2 className="text-red-600">{title}</h2>
}

export function Contentlet(contentlet) {
  const Component = Components[contentlet.contentType] || Fallback;
  return <Component {...contentlet} />
}

This component is going to be an intermediary to return the associated component content type of the contentlet we pass in. If the contentlet does does not exist it will return the Fallback component:

  1. Create a component map Components.

  2. Make a component to use as default in case we do not have the Fallback component created.

  3. In the Contentlet component we look for the component in the map using the contentType property. This is where we have to match the Content Type in dotCMS and the component in Next.js and if we don't have a matching Content Type, we return the Fallback component.

  4. Finally the "found" component or the Fallback is returned and the contentlet object is passed in as props.

Using .

Inside the component we iterate and render the containers and then iterate again to find the contentles inside the containers.

export default function Home({ layout, containers }) {
  const { rows } = layout.body;
  const containersData = containers;

  return (
    <>
      <Head>
        <title>DotCMS - Next.js & Tailwind CSS</title>
        <meta name="description" content="DotCMS - Next.js & Tailwind CSS" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <h1>DotCMS - Next.js & Tailwind CSS</h1>

      <main className="container m-auto">
        {rows.map(({ columns }, i) => (
          <Row key={`row-${i}`}>
            {columns.map(({ leftOffset, width, containers }, k) => (
              <Column leftOffset={leftOffset} width={width} key={`col-${k}`}>
                {containers.map(({ identifier, uuid }, l) => {
                  const contentlets =
                    containersData[identifier].contentlets[`uuid-${uuid}`];

                  return (
                    <div key={`container-${l}`}>
                      {contentlets.map((contentlet, m) => {
                        return <Contentlet {...contentlet} key={`contentlet-${m}`} />;
                      })}
                    </div>
                  );
                })}
              </Column>
            ))}
          </Row>
        ))}
      </main>
    </>
  );

To locate the contentlets we use the identifier and the uuid returning an array of contentlets that are rendered with the Contentlet component.

Conclusion

  1. Generate the page with Next.js

  2. Query the dotCMS APIs to get information from the page.

  3. With the page object you can use Tailwind grids to create the layout.

  4. Make a React component map for each type of content on the page.

The combination of Next.js with the powerful APIs of dotCMS allows you to create your pages quickly and if you supplement this with the grids system and CSS utilities of Tailwind, you have a powerful combination that allows you to launch your web project to the next level.

Freddy Montes
Senior Frontend Developer
August 02, 2021

Filed Under:

jamstack javascript nextjs

Recommended Reading

Benefits of a Multi-Tenant CMS and Why Global Brands Need to Consolidate

Maintaining or achieving a global presence requires effective use of resources, time and money. Single-tenant CMS solutions were once the go-to choices for enterprises to reach out to different market...

Headless CMS vs Hybrid CMS: How dotCMS Goes Beyond Headless

What’s the difference between a headless CMS and a hybrid CMS, and which one is best suited for an enterprise?

14 Benefits of Cloud Computing and Terminology Glossary to Get You Started

What is cloud computing, and what benefits does the cloud bring to brands who are entering into the IoT era?