Avatar

Hello, I'm Julia.

Setting Up a NextJS DatoCMS Static Page

#graphql #nextjs #typescript

4 min read

Here's a step by step process for how to create a NextJS static page, that pulls in content from DatoCMS, at build time.

Step 1: Install packages

The packages I used included:

  • react-datocms - A set of components and utilities to work faster with DatoCMS in React environments. Integrates seamlessly with DatoCMS's GraphQL Content Delivery API and Real-time Updates API.
  • graphql-request - Minimal GraphQL client supporting Node and browsers for scripts or simple apps.

Step 2: Set up GraphQL client

Create a new file with a request function that makes a GraphQL API call to DatoCMS. This is where you'll use the graphql-request library. Replace process.env.DATOCMS_API_TOKEN with your DatoCMS API token.

// datocms.ts

import { GraphQLClient } from 'graphql-request'

export function request({ query, variables }) {
  const endpoint = 'https://graphql.datocms.com/'

  const client = new GraphQLClient(endpoint, {
    headers: {
      authorization: `Bearer ${process.env.DATOCMS_API_TOKEN}`,
    },
  })
  return client.request(query, variables)
}

Step 3: Structure GraphQL query

Assuming you've set up your blocks and content in DatoCMS, it's super easy to then structure the GraphQL query you need by using Dato's API Explorer. Upon choosing the fields you want to receive from your DatoCMS content database, copy and paste the query into a new file and save it to a constant.

At the bottom of the file, export an async function that awaits for the API response from the query you've just structured.

// Homepage.ts

import { HomePageRecord } from './types'
import { request } from './datocms'

const HOMEPAGE_QUERY = `
{
  homePage {
    heroSection {
	  headline: {
	    value: string
	  }
      cta {
        buttons {
          __typename
          id
          buttonType
          label
          link
        }
      }
    }
    content {
      __typename
      ... on ImageAndTextBlockRecord {
        id
        backgroundColour {
          hex
        }
      }
      // etc etc etc
    }
  }
}
`

export const getHomepageContent = async (): Promise<HomePageRecord> => {
  const content = await request({
    query: HOMEPAGE_QUERY,
    variables: {},
  })

  return content.homePage
}

If you're using Typescript, here's an example of what the HomePageRecord looks like.

export type ButtonRecord = {
  __typename: 'ButtonRecord'
  id: string
  label: string
  link: string
  buttonType: 'primary' | 'secondary'
}

export type ButtonGroupRecord = {
  __typename: 'ButtonGroupRecord'
  id: string
  buttons: ButtonRecord[]
}

export type HomePageHeroSection = {
  cta: ButtonGroupRecord[]
  headline: {
    value: string
  }
}

export type HomePageRecord = {
  heroSection: HomePageHeroSection[]
  content: Array<HomePageContentRecord>
}

Step 4: Create NextJS path

In the pages directory, create the NextJS path where you want this DatoCMS content to be rendered. In this example, I wanted it to be the index page of the domain, so I created a new file in /pages/index.tsx.

The logic in the file states that if the request returns a response that includes a heroSection (required from the GraphQL query I structured), to send this response to the page component as props. If not, return a 404 page.

import { GetStaticProps } from 'next'
import { getHomepageContent } from '@/graphql/Homepage' // Homepage.ts file above
import { HomePage } from '@/components/HomePage' // new component to be created

export const getStaticProps: GetStaticProps = async () => {
  const pageContent = await getHomepageContent()

  if (pageContent?.heroSection) {
    return {
      props: {
        pageContent: pageContent,
      },
    }
  }

  return {
    notFound: true,
  }
}

export default HomePage

Step 5: Create the page component

Finally, create the HomePage page component that will be rendered when the above path is accessed. pageContent is passed down within the props per the above step.

import { Footer } from '@/components/elements/footer/Footer'
import { Header } from '@/components/elements/header/Header'
import { HomeContent } from '@/components/pages/home/HomeContent'
import { HomeHeroSection } from '@/components/pages/home/HomeHeroSection'
import { HomePageRecord } from './types' // defined in step 3

type HomePageProps = {
  pageContent: HomePageRecord
}

export const HomePage: React.FC<HomePageProps> = ({ pageContent }) => {
  return (
    <>
      <Header />
      <main>
        <HomeHeroSection content={pageContent.heroSection} />
        <HomeContent content={pageContent.content} />
      </main>
      <Footer />
    </>
  )
}

If you're wondering where the need for the react-datocms package comes in, it comes in handy when you actually get down to rendering DatoCMS blocks. For example, within the HomeHeroSection component, I needed to render a structured text block (which contains rich text content). There's a component called StructuredText that react-datocms provides, which allows us to directly pass in the response object and render the rich text content with all the expected formatting.

import { StructuredText } from 'react-datocms'
import { HomePageHeroSection } from './types'

type HomeHeroSectionProps = {
  content: HomePageHeroSection[]
}

export const HomeHeroSection = ({ content }: HomeHeroSectionProps) => {
  return (
    <StyledTextContent>
      <StructuredText data={heroSection.headline} />
      // ...other code bits here
    </StyledTextContent>
  )
}

© 2016-2024 Julia Tan · Powered by Next JS.