Skip to content
This repository has been archived by the owner on Mar 13, 2023. It is now read-only.

Contentful

Jakub Pokorski edited this page Jun 5, 2022 · 7 revisions

Basic Installation

To install contentful to your template, you must add dependencies and define some files first.

Dependencies:

yarn add contentful

Data models

The data model is our fancy way of saying predefined structure representing your whole page / series of components / whatever big piece of your application you want to query for. Usually, it is a whole page, layout or even all buttons.

To query your data model you need first a function to query data. Contentful came with a solution with their own package to getEntries with your query.

Usually in your application you only need two ways to query your content models. Query one content model or query all content models. To work better with typescript we come with a solution:

First you need a universal function to query data:

getData.ts

import type { Entry } from 'contentful'

import type { CONTENT_TYPE } from '$/types/generated/contentful'

const contentful = require('contentful')

const spaceId = process.env.CONTENTFUL_SPACE_ID
const accessToken = process.env.CONTENTFUL_ACCESS_TOKEN

if (!(spaceId && accessToken)) {
  throw Error(
    'Please configure your .env file with contentful or disable it for your project!',
  )
}

const client = contentful.createClient({
  space: spaceId,
  accessToken: accessToken,
})

const getData = async (
  contentType: CONTENT_TYPE,
): Promise<Entry<CONTENT_TYPE>[]> => {
  const TypeEntries = await client.getEntries<CONTENT_TYPE>({
    content_type: contentType,
    include: 10,
  })

  return TypeEntries.items
}

export default getData

And then two ways for querying:

getOneContentfulData.ts

import type { CONTENT_TYPE } from '$/types/generated/contentful'

import getData from './getData'

const getOneContentfulData = async <T>(
  contentType: CONTENT_TYPE,
): Promise<T> => {
  const data = await getData(contentType)

  /**
   *  JSON parse and stringify is used here to eliminate unexpected behavior from contentful
   **/
  return JSON.parse(
    JSON.stringify({
      fields: data[0]?.fields,
      sys: data[0]?.sys,
    }),
  )
}

export default getOneContentfulData

getManyContentfulData.ts

import type { CONTENT_TYPE } from '$/types/generated/contentful'

import getData from './getData'

const getManyContentfulData = async <T>(
  contentType: CONTENT_TYPE,
): Promise<T[]> => {
  const data = await getData(contentType)

  /**
   *  JSON parse and stringify is used here to eliminate unexpected behavior from contentful
   **/
  return Object.values(
    JSON.parse(
      JSON.stringify({
        ...data,
      }),
    ),
  )
}

export default getManyContentfulData

The way we are splitting this so much is because it will become much more readable at the end. Your final data model would look like this:

getLayoutData.ts

import type {
  ILayout,
  IHeader,
  IFooter,
  IButton,
} from '$/types/generated/contentful'

import getManyContentfulData from '../getManyContentfulData'
import getOneContentfulData from '../getOneContentfulData'

export interface ILayoutData {
  layout: ILayout
  header: IHeader
  footer: IFooter
  buttons: IButton[]
}

const getLayoutData = async (): Promise<ILayoutData> => {
  const [layout, header, footer, buttons] = await Promise.all([
    getOneContentfulData<ILayout>('layout'),
    getOneContentfulData<IHeader>('header'),
    getOneContentfulData<IFooter>('footer'),
    getManyContentfulData<IButton>('button'),
  ])

  return {
    layout,
    header,
    footer,
    buttons,
  }
}

export default getLayoutData

Typescript

To operate on types as we in examples above are highly suggesting and operating, we can use a library to generate types from the contentful itself.

Add this package as dev dependency:

yarn add --dev contentful-management contentful-typescript-codegen

Add command to your package.json:

"contentful-typescript-codegen": "contentful-typescript-codegen --output @types/generated/contentful.d.ts  && eslint --quiet --fix ./@types/generated/contentful.d.ts",

Then your script file:

getContentfulEnvironment.js

require('dotenv').config()
const contentfulManagement = require('contentful-management')

module.exports = function () {
  const contentfulClient = contentfulManagement.createClient({
    accessToken: process.env.CONTENTFUL_MANAGEMENT_API_ACCESS_TOKEN
  })

  return contentfulClient
    .getSpace(process.env.CONTENTFUL_SPACE_ID)
    .then(space => space.getEnvironment(process.env.CONTENTFUL_ENVIRONMENT))
}

In this script, we are using a couple of envs that you can get from the contentful service.

Usage on the final page

You can use getStaticProps to query contentful on the initial build:

export interface IHomePageProps extends ILayoutData {}

export const getStaticProps: GetStaticProps<IHomePageProps> = async () => {
  const layout = await getLayoutData()

  return {
    props: {
      layout,
    },
  }
}

Plain and simple 🎉

Easy to use 🌈

Flexible ⭐

Environmental variables

Filled with example data for context:

CONTENTFUL_SPACE_ID=gdewij512zpj
CONTENTFUL_ACCESS_TOKEN=RWpH4ANMXwFBDgh5jmlaC65qAeAtfGgZa_D5Pn2FtZY
CONTENTFUL_MANAGEMENT_API_ACCESS_TOKEN=CGAAP-iYL24G_V-2BQ2kl_VJMlzXuTBHnHQiM6JCq4-kD9Jl4
CONTENTFUL_ENVIRONMENT=master

Useful Contentful Components

ContentfulImage

import type { Asset } from 'contentful'
import Image from 'next/image'

/**
 * An easy way to include Contentful Image with Next.js Image tag
 *
 * @example
 * Where `data.fields.icon` contains an Asset from Contentful
 * <ContentfulImage {...data.fields.icon} />
 **/
const ContentfulImage = (props: Asset) => {
  if (!props.fields.file.details.image?.width) {
    return null
  }

  return (
    <Image
      src={'https:' + props.fields.file.url}
      alt={props.fields.title}
      width={props.fields.file.details.image?.width}
      height={props.fields.file.details.image?.height}
    />
  )
}

export default ContentfulImage