Was this page helpful?

Using Draft Mode to fetch unpublished content from Contentful APIs

Table of contents

Overview

To allow your users to preview content changes before publishing them, you will need to configure your application to fetch "preview" content from Contentful's APIs correctly.

You can then leverage Vercel's Draft Mode feature inside your application to adapt your Contentful API calls to fetch preview content only when Draft Mode is enabled.

How your specific applications constructs queries and fetches data from Contentful APIs will likely vary depending on your specific use case. This guide provides concrete examples that you can take as a starting point and hopefully adapt to your own application.

Prerequisites

You will need to provide a way for your content editors to "activate" Draft Mode in your application:

Configuring Contentful API keys for accessing preview content

Accessing Contentful's preview APIs requires a different access token than the token used for accessing production content. A preview token is included in every API key you create for your space.

Following is how you might provision an API key and incorporate it into your Vercel-hosted Next.js project.

In your Contentful space:

  1. Navigate to Settings > API Keys. Create a new API key or find the existing API key being used for your project.
  2. Take note of the values for "Content Delivery API - access token" and "Content Preview API - access token" for use in the next step.
  3. Navigate to Settings > General Settings
  4. Take note of the value for "Space ID"

In your Vercel project:

  1. Navigate to Settings > Environment Variables
  2. Create an environment variable with key CONTENTFUL_ACCESS_TOKEN and use the value you copied for "Content Delivery API - access token"
  3. Create an environment variable with key CONTENTFUL_PREVIEW_ACCESS_TOKEN and use the value you copied for "Content Preview API - access token"
  4. Create an environment variable with key CONTENTFUL_SPACE_ID and use the value you copied for "Space ID"

Accessing preview content via Contentful's GraphQL API

If you are using GraphQL to fetch Contentful content, you will need to adapt your GraphQL queries and HTTP requests to access preview content when Draft Mode is enabled.

In brief, you will need to adapt your Contentful GraphQL API calls to:

  • Use the preview access token instead of the default access token when accessing preview content in Draft Mode
  • Disable Next.js's default caching behavior when fetching preview content in Draft Mode
  • Adapt your GraphQL queries to include a preview parameter that should be set to true when your application is in Draft Mode

The example below illustrates what a working implementation of the above points might look like.

Note: For complete documentation on how to use Contentful's GraphQL API see the GraphQL reference documentation.

Example: GraphQL API

The examples below assume you followed the suggested guidance for provisioning an API key and adding the API tokens to your Vercel project as environment variables.

  1. Create a "preview aware" fetch utility function:
// lib/contentful/api.ts

// preview parameter should be called with `true` when Draft Mode is enabled
async function fetchGraphQL(query: string, variables = {}, preview = false) {
  const res = await fetch(
    `https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",

        // use the preview access token when preview is enabled
        Authorization: `Bearer ${
          preview
            ? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN
            : process.env.CONTENTFUL_ACCESS_TOKEN
        }`,
      },
      body: JSON.stringify({
        query,
        variables,
      }),

      // disable caching in preview mode (via `no-store`); otherwise use `force-cache` which is the default
      cache: preview ? "no-store" : "force-cache",
    }
  );

  return await res.json();
}
  1. Adapt your existing GraphQL queries to handle a "preview" boolean value correctly. (The example below assumes you have a "Blog Post" content model with some basic fields in Contentful.)
// lib/contentful/queries.ts

import { fetchGraphQl } from "@/lib/contentful/api";

interface BlogPost {
  __typename: string;
  sys: {
    id: string
  };
  slug: string;
  title: string;
  description: string;
}

interface BlogPostCollection {
  items: BlogPost[];
}

interface FetchResponse {
  data?: {
    blogPostCollection?: BlogPostCollection;
  };
}

const BLOG_POST_GRAPHQL_FIELDS = `
__typename
sys {
  id
}
slug
title
description
`;

function extractBlogPost(fetchResponse: FetchResponse): BlogPost | undefined {
  return fetchResponse?.data?.blogPostCollection?.items?.[0];
}

// preview parameters should be called with `true` when Draft Mode is enabled
export async function getBlogPostBySlug(slug: string, preview: boolean = false): Promise<BlogPost | undefined> {
  const entries = await fetchGraphQL(
    // the `preview` value is passed as an argument to the query to allow unpublished entries to be returned
    `query {
      blogPostCollection(where: { slug: "${slug}" }, preview: "${preview}", limit: 1) {
        items {
          ${BLOG_POST_GRAPHQL_FIELDS}
        }
      }
    }`,

    // the `preview` value is also passed as the third argument to the fetchGraphQL function to trigger correct access token and caching behavior
    preview,
  );
  return extractBlogPost(entries);
}
  1. When fetching Contentful content via GraphQL in your compoment, retrieve and pass through the current Draft Mode state as the preview parameter:
// app/blogs/[slug]/post.tsx

import { draftMode } from "next/headers";
import { getBlogPostBySlug } from "@/lib/contentful/queries";

interface BlogPostProps {
  params: { slug: string };
}

export default async function BlogPost({ params }: BlogPostProps) {
  const { isEnabled: isDraftModeEnabled } = draftMode();
  const blogPost = await getBlogPostBySlug(params.slug, isDraftModeEnabled);

  return (
    <main>
      <h1>{blogPost.title}</h1>
      <div>{blogPost.description}</div>
    </main>
  );
}

You can adapt the above examples to your own use case, but the following files should hopefully make clear how you can take the state of Draft Mode from your page route and then adapt your query and API calls to ensure that preview content is fetched properly from Contentful.

Accessing preview content via the Content Delivery/Preview API

If you are using Contentful's Content Delivery API Javascript SDK to fetch content entries inside your Next.js application, you will need to make a few adjustments to your application to access preview content when Draft Mode is enabled.

In brief you will need to:

  • Use the preview access token instead of the default access token when initializing the Contentful.js client in your application when Draft Mode is enabled
  • Specify the preview host (preview.contentful.com) during the same initialization call, again when Draft Mode is enabled.

The example below illustrates what a working implementation of the above points might look like.

Example: Content Delivery API Javascript SDK

Note: This example assumes you have installed and are using Contentful's Content Delivery API Javascript SDK in your Next.js application.
  1. Create a "preview aware" Contentful client initialization function:
import { createClient } from "contentful";

// preview parameter should be called with `true` when Draft Mode is enabled
export default function createContentfulClient(preview: boolean = false) {
  return createContentfulClient({
    space: process.env.CONTENTFUL_SPACE_ID,

    // use the preview access token when preview is enabled
    accessToken: preview
      ? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN
      : process.env.CONTENTFUL_ACCESS_TOKEN,


    // specify the preview host when preview is enabled
    host: preview ? "preview.contentful.com" : undefined,
  });
}
  1. Adapt your existing queries to handle a "preview" boolean value correctly. (The example below assumes you have a "Blog Post" content model with some basic fields in Contentful.)
// lib/contentful/queries.ts

import { EntryFieldTypes, Entry } from "contentful";
import createContentfulClient from "@/lib/contentful/client";

interface BlogPost {
  contentTypeId: 'blogPost';
  fields: {
    slug: EntryFieldTypes.Text,
    title: EntryFieldTypes.Text,
    description: EntryFieldTypes.Text,
  }
};

export async function getBlogPostBySlug(
  slug: string,
  preview: boolean = false
): Promise<Entry<BlogPost> | undefined> {
  const client = createContentfulClient(preview);
  const entries = await client.getEntries<BlogPost>({
    content_type: 'blogPost',
    'fields.slug': slug,
  });
  return entries.items[0];
}
  1. When fetching Contentful content in your compoment, retrieve and pass through the current Draft Mode state as the preview parameter:
// app/blogs/[slug]/post.tsx

import { draftMode } from "next/headers";
import { getBlogPostBySlug } from "@/lib/contentful/queries";

interface BlogPostProps {
  params: { slug: string };
}

export default async function BlogPost({ params }: BlogPostProps) {
  const { isEnabled: isDraftModeEnabled } = draftMode();
  const blogPost = await getBlogPostBySlug(params.slug, isDraftModeEnabled);

  return (
    <main>
      <h1>{blogPost.title}</h1>
      <div>{blogPost.description}</div>
    </main>
  );
}

You can adapt the above examples to your own use case, but the following files should hopefully make clear how you can take the state of Draft Mode from your page route and then adapt your query and API calls to ensure that preview content is fetched properly from Contentful.