Ian J. MacIntosh

Getting Content from Contentful into Eleventy

I wanted my personal site to pull content from a CMS instead of having to manage a bunch of content files in my project's filesystem. I found a really excellent article (Integrating Contentful with Eleventy to create static sites) on Contentful's website explaining how to do this, but wanted to share my own experience and an abbreviated version of the steps.

Prerequisites

Since this article is about getting content from Contentful into Eleventy, I presume you have an Eleventy project running and a Contentful space with content in it.

Steps

  1. Add Contentful Plugin
  2. Store Keys Safely
  3. Get Data from Contentful
  4. Make an Index
  5. Make Pages
  6. Work with Dates (Optional)

Step 1: Add Contentful plugin 🔌

Install the Contentful JavaScript Delivery SDK package:

npm install --save-dev contentful

Step 2: Store keys safely 🔑

Find your Space ID and API access tokens in the Contentful admin panel, under Settings > API keys.

You need to store these secret values somewhere the Contentful JavaScript Delivery SDK can find them to authenticate itself with Contentful. I like to store them as environment variables in a local .env file and use dotenv to manage them. You don't need to do it this way, it's just the simplest way I know.

Install the dotenv package:

npm install --save-dev dotenv

If you're using git for source control, update your .gitignore file to ignore the .env file you're about to create. This is important so you won't store sensitive info in your repo.

Example .gitignore:
node_modules/
_site/
.env

Now you're ready to make your .env file. Here's a starter template, replace the placeholder values with your space ID and access token. Again, these can be found in the Contentful admin panel, under Settings > API keys.

Example .env:
 # Content Delivery API host:
 CTFL_HOST="cdn.contentful.com"

 # Contentful Space ID:
 CTFL_SPACE="space_id_placeholder"

 # Content Delivery API access token:
 CTFL_ACCESSTOKEN="content_delivery_api_access_token_placeholder"

This .env setup is what I use on my local dev environment, but if you're hosting your site with a third party, you may prefer to use their control panel for managing environment variables.


Step 3: Get Data from Contentful 🚅

Add a new data source by adding a new file to your /_data directory. I named mine contentful.js because that's what the tutorial did.

Example /_data/contentful.js:
require("dotenv").config();

const contentful = require("contentful");
const client = contentful.createClient({
  host: process.env.CTFL_HOST,
  space: process.env.CTFL_SPACE,
  accessToken: process.env.CTFL_ACCESSTOKEN,
});

module.exports = function () {
  return client
    .getEntries({
      content_type: "article",
    })
    .then(function (response) {
      const items = response.items.map(function (item) {
        console.log("🍾");
        return item;
      });

      console.log(`Got ${items.length} items from Contentful`);
      return items;
    })
    .catch(console.error);
};

See the getEntries() call? The content_type property tells Contentful what data you want. Replace article with post or whatever your content type ID is. If you're not sure, check in the Contentful admin panel, under Content model and select your desired content type. When I wrote this, it was on the right side of the page.

If you haven't already done so, start your Eleventy server:

eleventy --serve

If the data call works, your console window running your server should show some champagne bottles and "Got 4 items from Contentful" in your console (or however many published items are available). If you have no published items, you'll get 0 champagne bottles and might want to switch to the Content Preview API for testing purposes. See below for more on that.

Once you confirm things work, you can remove the console.log statements and begin the next step, working with this data to build an index.

Planning on Working with Unpublished Content? 👀

You can get unpublished content from Contentful by calling the Content Preview API instead of the Content Delivery API (which only exposes published content).

If you'd like to use the Content Preview API, switch your CTFL_HOST value to preview.contentful.com and your CTFL_ACCESSTOKEN to your Content Preview API access token. You can find this with the other values, in the Contentful admin panel, under Settings > API keys. I add duplicate lines for in my .env file, and when I want to switch between Content Preview API and Content Delivery API, I comment/uncomment the relevant lines.

Example .env for working with unpublished content:
# Contentful "master" Space ID:
CTFL_SPACE="abc123def456"

# Content Delivery API host:
# CTFL_HOST="cdn.contentful.com"

# Content Delivery API access token:
# CTFL_ACCESSTOKEN="abcdefghijklmnopqrstuvwxyz1234567890"

# Content Preview API host:
CTFL_HOST="preview.contentful.com"

# Content Preview API access token:
CTFL_ACCESSTOKEN="0987654321abcdefghijklmnopqrstuvwxyz"

Step 4: Make an Index 📖

You can build a list of pages from your data using Eleventy's pagination feature. I use Nunjucks for a templating language in this example to build logic into my templates.

My example will only work with 2 or more items in the data; the for statement in Nunjucks won't work with only one item. If you only have one item, you may want to jump to the next step.

Example /all-pages.njk:
---
pagination:
  data: contentful
  size: 10
---
<!DOCTYPE html>
<html lang="en">
<body>
<ul>
{% for article in pagination.items %}
<li>
    <a href="/{{ article.fields.title | slug }}/">
        {{ article.fields.title }}
    </a>
</li>
{% endfor %}
</ul>
{% if pagination.href.previous %}
<p>
    <a href="{{ pagination.href.previous }}">Previous</a>
</p>
{% endif %}
{% if pagination.href.next %}
<p>
    <a href="{{ pagination.href.next }}">Next</a>
</p>
{% endif %}
</body>
</html>

Explanation

Once you navigate to /all-pages/ (or whatever you named yours) and confirm your list of all pages shows broken links for your content, you're ready to make the pages themselves.


Step 5: Make Pages 📄

I use pagination again to build the pages themselves, specifying a size of 1 to put one item on each page.

Example /page.njk:
---
pagination:
  data: contentful
  size: 1
  alias: article

templateEngineOverride: njk,md
permalink: "/{{ article.fields.title | slug }}/"
eleventyComputed:
  title: "{{ article.fields.title }}"
  content: "{{ article.fields.markdownBody }}"
---
<!DOCTYPE html>
<html lang="en">
<body>
<h1>{{ title }}</h1>
    {{ content }}
</body>
</html>

Explanation

To test, go to your console and confirm a new page is generated for each item retrieved from Contentful. (e.g., Writing _site/getting-content-from-contentful-into-eleventy/index.html from ./page.njk.) Navigate to that URL (/getting-content-from-contentful-into-eleventy/index.html) in your web browser.

That's it, you're done! 🏁

Due to the static nature of Eleventy, you'll need to generate your site again to see changes after modifying your content in Contentful.


Optional: Use Moment to Work with Dates 🗓

To show Published on Dec 25, 2018 on my pages, I use a Moment plugin for Nunjucks. Since Contentful stores dates in "2020-09-03T03:53:54.665Z" format, I wanted to show that same information in a way that's a little easier on the eyes. I've had nothing but good experiences with Moment even though it's probably overkill for this use case.

Install the Nunjucks date filter package:

npm install --save-dev nunjucks-date-filter

In your .eleventy.js configuration file, add this new filter:

const dateFilter = require("nunjucks-date-filter");

module.exports = function (eleventyConfig) {
  eleventyConfig.addNunjucksFilter("date", dateFilter);
}

Use it in your templates:

Published {{ published | date('MMM D, YYYY') }}