> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://contentful.com/developers/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://contentful.com/_mcp/server.
# Building a website with Compose in Next.js
> Build a website with Compose in Next.js | Routing | Fetching content | Rendering content, metadata | Creating a new page type
> **Info**
>
> Compose is being deprecated. It is in maintenance mode and won’t be updated with any new features. Current installations will work until the end of 2026.
In this guide you will learn how to develop a website using Compose. Contentful provides an example implementation of a Help Center website in the [contentful/compose-starter-helpcenter-nextjs](https://github.com/contentful/compose-starter-helpcenter-nextjs) GitHub repository. This is an application built with the [Next.js](https://nextjs.org/) framework which is powered by [React](https://reactjs.org/).
## Routing
Each article and landing page has a unique `slug` text field that describes and identifies the given page. You can use `slug` values as part of page URLs in your website for a user- and SEO-friendly navigation. For example, on the Help Center website the “Introduction to Contentful” article will be available under the `/articles/introduction-to-contentful` URL.
If your website supports multiple languages you may also want to include a `locale` param in the URL. See our documentation about [Localization with Contentful](/tutorials/general/setting-locales/) for more information.
## Fetching content
Contentful allows you to fetch all the content needed on a given web page in one request (with certain [limits](/developers/docs/technical-limits/)). You can make a `GET` request to [Content Delivery API](/references/content-delivery-api/overview) directly or use the [JavaScript client library](https://contentful.github.io/contentful.js/contentful/7.14.8/), as in the following example:
```
import { createClient } from 'contentful';
const client = createClient({
space: CF_SPACE_ID, // ID of a Compose-compatible space to be used \
accessToken: CF_DELIVERY_ACCESS_TOKEN, // delivery API key for the space \
});
type GetPageParams = {
pageContentType: string;
slug: string;
locale: string;
};
async function getPage(params: GetPageParams) {
const query = {
limit: 1,
include: 10,
locale: params.locale,
'fields.slug': params.slug,
content_type: params.pageContentType,
};
const { items: [page] } = await client.getEntries(query);
return page || null;
}
```
View the full code in [/src/lib/api.ts](https://github.com/contentful/compose-starter-helpcenter-nextjs/blob/master/src/lib/api.ts)
You can then use the `getPage` function to fetch a given page. For example, when a user navigates to `/articles/introduction-to-contentful` URL on the Help Center website, we fetch a `Helpdesk Article` page with the `introduction-to-contentful` slug:
```
const page = await getPage({
pageContentType: 'page_help_center_article',
slug: 'introduction-to-contentful',
locale: 'de-DE',
});
```
### Preview mode
If you want to support previewing unpublished content on your website, create a second Contentful client:
```
const previewClient = createClient({
space: CF_SPACE_ID, // ID of a Compose-compatible space to be used
accessToken: CF_PREVIEW_ACCESS_TOKEN, // preview API key for the space
host: 'preview.contentful.com',
});
```
In the Help Center example we rely on a query string parameter `?preview=1` to decide which Contentful client to use for fetching content.
## Rendering content
Once the correct data is fetched for the page you requested, you can render it in your website. In the Help Center example, we traverse the `page` object that we retrieved recursively and use [React](https://reactjs.org/) to render individual entries that make the page. There’s a React component defined for each Content Type:
```
const ContentTypeMap = {
'page_help_center_article': HelpCenterArticle,
'component_text': Text,
'component_image': Image,
'component_video': Video,
...
};
```
View the full code in [/src/components/renderer/block-renderer.tsx](https://github.com/contentful/compose-starter-helpcenter-nextjs/blob/master/src/components/renderer/block-renderer.tsx)
Below is an example of a simple React component rendering a `Text` Content Type. It includes rendering a RichText field as HTML using the [@contentful/rich-text-react-renderer](https://www.npmjs.com/package/@contentful/rich-text-react-renderer) package:
```
import React from 'react';
import { documentToReactComponents as renderRichText } from '@contentful/rich-text-react-renderer';
function Text(textEntry) {
const { title, text } = textEntry.fields;
return (
<>
{title ?
{title}
: null}
{renderRichText(text)}
>
);
}
```
## Rendering metadata
In addition to the `page` content itself, we can also use the metadata that is stored in the `title` and `seo` fields. Below is an example of how to use those values on a web page:
```
const seo = page.fields.seo.fields;
const { description = '', keywords = [], title = page.fields.title} = seo;
const robots = [
seo.no_index === true ? 'noindex' : undefined,
seo.no_follow === true ? 'nofollow' : undefined,
].filter((x) => x !== undefined);
...
{title}
{robots.length > 0 && }
{description.trim() !== '' && (
)}
{keywords.length > 0 && }
```
View the full code in [/src/components/page-head.tsx](https://github.com/contentful/compose-starter-helpcenter-nextjs/blob/master/src/components/page-head.tsx)
## Create a new page type and render it on the frontend
Let's wrap up all the information and see an end-to-end use case where we start publishing pages of a different type.
By this time, the following steps might look familiar.
In our case we want to add tutorials to our Help Center, so the pages will display the walk-trough text, a difficulty indicator and, if available, a link to the repo where users can find the tutorial material.
### Step 1: Create the new page type
Open your target space in the [Contentful web app](https://app.contentful.com/). Then navigate to the "Content Model" tab and click "Add content type". Enter your page type name, for us it's "Tutorial".

### Step 2: Make the content type available as page type in Compose
Go to Compose and switch to the page types view. Click on the "Select content type", pick the "Tutorial" content type and finish the configuration to make it available as a page type. Learn more about how to [Set up page types in Compose](/help/set-up-page-types-in-compose).

### Step 3: Create and edit a new page
It's time to get creative, open Compose and create a page with the new type. Fill in the fields with the content you want to serve in your website. Then publish the page.

### Step 4: Render your content
To visualize our data, we need to add a new component to our Next.js app:
```
/* new file: src/pages/[locale]/tutorials/[slug].tsx */
import React from 'react';
import { TypePage } from 'lib/types';
import { withLocale } from 'lib/translations/locales';
import { isPreviewEnabled } from '../../../lib/preview';
import { getPage } from 'lib/api';
import { PageContentTypes } from '../../../lib/constants';
import ErrorPage from 'next/error';
import { PageHead } from '../../../components/page-head';
import { isRichText, renderRichText } from '../../../lib/rich-text/render';
/*
The new Tutorial component, it renders the data fetched by getServerSideProps
See https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering
*/
const Tutorial = ({ page }: { page: TypePage }) => {
if (!page) {
//getServerSideProps did not find the page
return
}
const {
difficulty,
timeRequired,
explanation,
repoLink,
} = page.fields
/* Visualize the data as you see fit, sky is the limit */
return (
<>
{/* We reuse the existing code for title and seo metadata rendering in the header */}
Tutorial
{page.fields.title}
{/*Turn difficulty into a 3-dots rating*/}