Using Draft Mode to fetch unpublished content from Contentful APIs
Table of contents
- Overview
- Prerequisites
- Configuring Contentful API keys for accessing preview content
- Accessing preview content via Contentful's GraphQL API
- Accessing preview content via the Content Delivery/Preview API
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:
- You can install the Vercel app and follow our guidance for setting up a route handler to activate Draft mode in a separate topic in this guide.
- You can follow Vercel's instructions for enabling Draft Mode in the Vercel Toolbar. (Note that the Vercel toolbar is not compatible with Contentful's side by side preview features, including Live Preview).
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:
- Navigate to Settings > API Keys. Create a new API key or find the existing API key being used for your project.
- Take note of the values for "Content Delivery API - access token" and "Content Preview API - access token" for use in the next step.
- Navigate to Settings > General Settings
- Take note of the value for "Space ID"
In your Vercel project:
- Navigate to Settings > Environment Variables
- Create an environment variable with key
CONTENTFUL_ACCESS_TOKEN
and use the value you copied for "Content Delivery API - access token" - Create an environment variable with key
CONTENTFUL_PREVIEW_ACCESS_TOKEN
and use the value you copied for "Content Preview API - access token" - 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 totrue
when your application is in Draft Mode
The example below illustrates what a working implementation of the above points might look like.
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.
- 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();
}
- 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);
}
- 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
- 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,
});
}
- 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];
}
- 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.