Why I Chose Contentful CMS
Initially, I created a portfolio and blog using Gatsby.js, managing markdown files directly within the project. When planning a new version of my portfolio site, I wanted to manage content more efficiently, which led me to explore CMS options. Among them, I chose Contentful. I also decided to adopt Next.js together.
Initial Setup
Environment Variable Configuration
Obtain the necessary tokens from the Contentful dashboard and add them to .env.local
.
CONTENTFUL_SPACE_ID=YOUR_SPACE_ID
CONTENTFUL_ACCESS_TOKEN=YOUR_ACCESS_TOKEN
CONTENTFUL_PREVIEW_ACCESS_TOKEN=YOUR_PREVIEW_TOKEN # Optional
The Preview token is for checking content changes in real-time and can be set up as needed.
Implementation of Data Fetching
1. SDK Method (Initial Implementation)
// utils/contentful.ts
import { createClient } from 'contentful';
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
})
export const getBlogPosts = async () => {
const results = await client.getEntries({
content_type: 'blogPost',
})
return results.items
}
2. REST API Method (Final Implementation)
I switched to the REST API method because the SDK method didn’t work in the Cloudflare Pages deployment environment.
// utils/contentful.ts
const CONTENTFUL_SPACE_ID = process.env.CONTENTFUL_SPACE_ID!;
const CONTENTFUL_ACCESS_TOKEN = process.env.CONTENTFUL_ACCESS_TOKEN!;
export async function fetchPosts() {
const url = `https://cdn.contentful.com/spaces/${CONTENTFUL_SPACE_ID}/environments/master/entries`;
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${CONTENTFUL_ACCESS_TOKEN}`,
},
query: {
access_token: CONTENTFUL_ACCESS_TOKEN,
content_type: 'blogPost'
}
});
if (!response.ok) {
throw new Error(`Failed to fetch posts: ${response.statusText}`);
}
return response.json();
}
export async function fetchPostBySlug(slug: string) {
const url = `https://cdn.contentful.com/spaces/${CONTENTFUL_SPACE_ID}/environments/master/entries`;
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${CONTENTFUL_ACCESS_TOKEN}`,
},
query: {
access_token: CONTENTFUL_ACCESS_TOKEN,
content_type: 'blogPost',
'fields.slug': slug
}
});
if (!response.ok) {
throw new Error(`Failed to fetch post: ${response.statusText}`);
}
const data = await response.json();
const post = data.items[0];
if (!post) {
throw new Error("Post not found");
}
return post;
}
Using in Next.js App Router
const Blog: React.FC = async () => {
const posts = await fetchPosts();
// Data processing logic
return (
// JSX
);
}
Improvements
- Enhanced error handling
- Added type safety
- Implemented caching strategy