@se-studio/contentful-rest-api
v1.0.150
Published
Type-safe Contentful REST API client with caching and rate limiting for Next.js applications
Maintainers
Readme
@se-studio/contentful-rest-api
Type-safe Contentful REST API client with caching, rate limiting, and extensible converters for Next.js applications.
Table of Contents
- Features
- Installation
- Quick Start
- API Reference
- Converter Pattern
- Caching
- Error Handling
- Retry Configuration
- Rate Limiting
- Type Generation
- Configuration Options
- Next.js App Router Integration
- License
- Repository
Features
- 🎯 Type-safe - Full TypeScript support with generated types from your Contentful space
- 🔄 Dual API Support - Content Delivery API (CDA) and Content Preview API (CPA)
- ⚡ Next.js Optimized - Built-in support for Next.js App Router cache tags and revalidation
- 🔁 Retry Logic - Automatic retry with exponential backoff for failed requests
- 🚦 Rate Limiting - Respect Contentful API rate limits with built-in rate limiter
- 🧩 Extensible Converters - Functional composition pattern for customizing content transformations
- 🛡️ Error Handling - Custom error types for different failure scenarios
Installation
pnpm add @se-studio/contentful-rest-api @se-studio/core-data-types contentfulQuick Start
Basic Usage
import { contentfulPageRest } from '@se-studio/contentful-rest-api';
// Fetch a page by slug
const page = await contentfulPageRest(
{
spaceId: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
environment: 'master'
},
'home',
{
locale: 'en-US',
cache: {
tags: ['home-page'],
revalidate: 3600 // 1 hour
}
}
);Preview Mode
import { contentfulPageRest } from '@se-studio/contentful-rest-api';
// Use preview API for draft content
const page = await contentfulPageRest(
{
spaceId: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN!,
},
'home',
{
preview: true, // Uses Preview API (CPA)
cache: {
cache: 'no-store' // Disable Next.js caching for preview content
}
}
);API Reference
Client Functions
createContentfulClient(config)
Creates a Contentful Content Delivery API (CDA) client.
import { createContentfulClient } from '@se-studio/contentful-rest-api';
const client = createContentfulClient({
spaceId: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
environment: 'master'
});createContentfulPreviewClient(config)
Creates a Contentful Content Preview API (CPA) client.
import { createContentfulPreviewClient } from '@se-studio/contentful-rest-api';
const previewClient = createContentfulPreviewClient({
spaceId: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN!,
});Content Fetching Functions
contentfulPageRest(config, slug, options?, converter?)
Fetches a page by slug.
Parameters:
config: ContentfulConfig - Contentful client configurationslug: string - Page slug to fetchoptions?: FetchOptions - Optional fetch options (locale, preview, cache, retry)converter?: Converter - Optional custom converter function
Returns: Promise<IPage | null>
const page = await contentfulPageRest(
config,
'about-us',
{
locale: 'en-US',
include: 10,
cache: { tags: ['about-page'], revalidate: 3600 }
}
);contentfulPageByIdRest(config, id, options?, converter?)
Fetches a page by entry ID.
Parameters:
config: ContentfulConfigid: string - Entry IDoptions?: FetchOptionsconverter?: Converter
Returns: Promise<IPage>
Throws: EntryNotFoundError if entry doesn't exist
const page = await contentfulPageByIdRest(
config,
'5nZHNlP9rZhWvKx4w2Z8zB'
);contentfulAllPagesRest(config, options?, converter?)
Fetches all pages from Contentful.
Parameters:
config: ContentfulConfigoptions?: FetchOptionsconverter?: Converter
Returns: Promise<IPage[]>
const pages = await contentfulAllPagesRest(
config,
{
locale: 'en-US',
cache: { tags: ['all-pages'], revalidate: 3600 }
}
);contentfulEntryRest<TEntry, TResult>(config, id, converter, options?)
Generic function to fetch any entry type with a custom converter.
const article = await contentfulEntryRest(
config,
'articleId123',
myArticleConverter,
{ locale: 'en-US' }
);Converter Pattern
The package uses a functional composition pattern for converting Contentful entries to your domain types.
Base Converter
import { basePageConverter } from '@se-studio/contentful-rest-api';
// Use the base converter
const page = basePageConverter(contentfulEntry);Enhancers
Enhancers are functions that wrap converters to add additional functionality:
import {
basePageConverter,
withSEO,
withSections,
withTags,
compose
} from '@se-studio/contentful-rest-api';
// Compose multiple enhancers
const myConverter = compose(
withSEO,
withSections,
withTags
)(basePageConverter);
// Use with API functions
const page = await contentfulPageRest(config, 'home', {}, myConverter);Custom Converters
Create your own converter enhancers:
import type { Converter, PageEntrySkeleton } from '@se-studio/contentful-rest-api';
import type { IPage } from '@se-studio/core-data-types';
function withCustomField(
converter: Converter<PageEntrySkeleton, IPage>
): Converter<PageEntrySkeleton, IPage> {
return (entry) => {
const page = converter(entry);
// Add custom logic
return {
...page,
customField: entry.fields.customField || 'default'
};
};
}
// Use it
const converter = withCustomField(basePageConverter);Caching
Cache Tags
The package automatically manages cache tags for Next.js App Router. Tags follow a consistent naming convention:
- Individual entries:
{contentType}#{slug}(e.g.,page#home,article#my-post) - Collections:
{contentType}(e.g.,page,article,articleType) - Preview mode:
global(single tag for all content when preview mode is enabled)
Automatic Cache Tagging
All content fetching functions automatically apply appropriate cache tags based on the preview flag:
import { contentfulPageRest, contentfulArticleRest } from '@se-studio/contentful-rest-api';
// Production mode: tagged with ['page#home', 'page']
const page = await contentfulPageRest(config, 'home', { preview: false });
// Preview mode: tagged with ['global']
const previewPage = await contentfulPageRest(config, 'home', { preview: true });Preview Mode
When preview: true is passed to fetch functions or revalidation:
- All content uses the
globalcache tag - Revalidation always clears the
globaltag - No time-based revalidation
Cache Revalidation
Webhook Integration
Set up Contentful webhooks to automatically revalidate cache when content changes:
- Create API Route in your Next.js app:
// app/api/revalidate/route.ts
import { createRevalidationHandler } from '@se-studio/contentful-rest-api';
export const POST = createRevalidationHandler({
secret: process.env.REVALIDATION_SECRET,
validateSecret: true,
preview: process.env.DRAFT_ONLY === 'true', // or from your config
});Configure Contentful Webhook:
- URL:
https://yourdomain.com/api/revalidate - Secret: Set
REVALIDATION_SECRETenvironment variable - Triggers: Select content types to monitor (pages, articles, etc.)
- URL:
Required Environment Variables:
REVALIDATION_SECRET=your-webhook-secret
CONTENTFUL_MANAGEMENT_TOKEN=your-management-token
CONTENTFUL_SPACE_ID=your-space-id
CONTENTFUL_ENVIRONMENT_NAME=your-environmentManual Revalidation
Revalidate cache tags programmatically:
'use server'
import { revalidateTags, revalidateSingleTag } from '@se-studio/contentful-rest-api';
// Revalidate multiple tags
await revalidateTags(['page#home', 'page'], 'manual revalidation');
// Revalidate single tag
await revalidateSingleTag('article#my-post', 'article updated');Bulk Revalidation
Trigger full cache revalidation:
# Via HTTP request
curl -X POST https://yourdomain.com/api/revalidate \
-H "REVALIDATE_ALL: true"
# Via single tag
curl -X POST https://yourdomain.com/api/revalidate \
-H "REVALIDATION_TAG: global"Content Type Support
The revalidation system supports these content types:
- Pages:
page(individual:page#{slug}, collection:page) - Articles:
article(individual:article#{slug}, collection:article) - Article Types:
articleType(individual:articleType#{slug}, collection:articleType) - Custom Types:
customType(collection only:customType) - Tags:
tag(individual:tag#{slug}, collection:tag) - People:
person(individual:person#{slug}, collection:person) - Assets:
asset(individual:asset#{id}, collection:asset) - Locations:
location(individual:location#{slug}, collection:location)
Tag Functions
Access tag generation functions directly:
import {
pageTag,
articleTag,
articleTypeTag,
PageTag,
ArticleTag,
AllTags
} from '@se-studio/contentful-rest-api';
// Individual tags
const homePageTag = pageTag('home'); // 'page#home'
const blogPostTag = articleTag('my-post'); // 'article#my-post'
// Collection tags
const allPagesTag = PageTag; // 'page'
const allArticlesTag = ArticleTag; // 'article'
// All available tags
console.log(AllTags); // ['page', 'article', 'articleType', ...]Error Handling
The package provides custom error types for different scenarios:
import {
ContentfulError,
RateLimitError,
EntryNotFoundError,
AuthenticationError,
ValidationError,
isRetryableError
} from '@se-studio/contentful-rest-api';
try {
const page = await contentfulPageByIdRest(config, 'invalid-id');
} catch (error) {
if (error instanceof EntryNotFoundError) {
console.log('Entry not found:', error.entryId);
} else if (error instanceof RateLimitError) {
console.log('Rate limited, retry after:', error.retryAfter);
} else if (isRetryableError(error)) {
// Handle retryable errors
}
}Retry Configuration
Configure retry behavior for resilient API calls:
const page = await contentfulPageRest(
config,
'home',
{
retry: {
maxRetries: 3,
initialDelay: 1000,
maxDelay: 30000,
backoffMultiplier: 2
}
}
);Rate Limiting
Use the built-in rate limiter to control request rates:
import { RateLimiter } from '@se-studio/contentful-rest-api';
// Create a rate limiter (5 requests per second)
const limiter = new RateLimiter(5, 1);
// Consume a token before making a request
await limiter.consume();
const page = await contentfulPageRest(config, 'home');Type Generation
Generate TypeScript types from your Contentful space:
1. Configure Environment Variables
Create a .env.local file:
CONTENTFUL_SPACE_ID=your-space-id
CONTENTFUL_MANAGEMENT_TOKEN=your-management-token
CONTENTFUL_ENVIRONMENT_NAME=master2. Generate Types
pnpm --filter @se-studio/contentful-rest-api generate:typesThis creates src/generated/contentful-types.ts with types matching your Contentful content model.
3. Use Generated Types
import type { TypePageSkeleton } from './generated/contentful-types';
import { contentfulEntryRest } from '@se-studio/contentful-rest-api';
const page = await client.getEntry<TypePageSkeleton>('entry-id');Configuration Options
ContentfulConfig
interface ContentfulConfig {
spaceId: string;
accessToken: string;
environment?: string; // defaults to 'master'
host?: string; // for proxies or custom endpoints
options?: Partial<CreateClientParams>;
}FetchOptions
interface FetchOptions {
locale?: string;
preview?: boolean;
include?: number; // include depth for linked entries (default: 10)
cache?: CacheConfig;
retry?: RetryConfig;
enrichPictureBlurPlaceholders?: boolean;
}Picture blur placeholders (enrichPictureBlurPlaceholders) — When true, each included raster IPicture from Contentful (*.ctfassets.net image URLs) triggers an extra cached fetch of a tiny JPEG (width ~24px) to fill blurDataURL for @se-studio/core-ui / next/image. Skip with false or omit (default) to avoid those requests. Reuses the same asset cache tags as other asset enrichment so webhooks can still invalidate. Pictures that already define blurDataURL are left unchanged.
CacheConfig
interface CacheConfig {
tags?: string[]; // Next.js cache tags
revalidate?: number | false; // ISR revalidation time in seconds
cache?: 'force-cache' | 'no-store';
}RetryConfig
interface RetryConfig {
maxRetries?: number; // default: 3
initialDelay?: number; // default: 1000ms
maxDelay?: number; // default: 30000ms
backoffMultiplier?: number; // default: 2
}Next.js App Router Integration
Example usage in a Next.js Server Component:
// app/[slug]/page.tsx
import { contentfulPageRest } from '@se-studio/contentful-rest-api';
interface PageProps {
params: { slug: string };
}
export default async function Page({ params }: PageProps) {
const page = await contentfulPageRest(
{
spaceId: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
},
params.slug,
{
cache: {
tags: [`page:${params.slug}`],
revalidate: 3600
}
}
);
if (!page) {
notFound();
}
return (
<div>
<h1>{page.title}</h1>
<p>{page.description}</p>
</div>
);
}
export async function generateStaticParams() {
const pages = await contentfulAllPagesRest({
spaceId: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
});
return pages.map((page) => ({
slug: page.slug,
}));
}API Reference
Main Exports
API Functions
contentfulPageRest- Fetches a page from Contentful by slug; if none exists, resolves a page variant (merged ontooriginalPagewithalternativeContentsswaps) for the same slugcontentfulArticleRest- Fetches an article by slug and article type slugcontentfulArticleTypeRest- Fetches an article type by slugcontentfulAllPagesRest- Fetches all pages from ContentfulcontentfulAllPageLinks- Fetches all page links plus page variant links (metadata only), deduped by slug (page wins)contentfulAllArticleLinks- Fetches all article links (lightweight metadata only — no full content). ReturnsIContentfulArticleLink[](extendsIArticleLink) includingtitle,subtitle,date,tags,articleType,featuredImage(asvisual),visuals,description, and optionalsummary(rich text). Used for listing/browsing UIs. Exposed in apps viagetAllArticleLinksfromcreateAppHelpers.getBreadcrumbLookup- Fetches a map of path → breadcrumb label for resolving breadcrumb segments from URL paths. Uses same caching as sitemap. Use withresolveBreadcrumbSegmentsfrom@se-studio/core-ui.
Client Functions
createContentfulClient- Creates a Contentful Content Delivery API (CDA) clientcreateContentfulPreviewClient- Creates a Contentful Content Preview API (CPA) clientgetContentfulClient- Gets the appropriate client based on preview mode
Converter Functions
createBaseConverterContext- Creates base converter context with default resolversbasePageConverter- Base converter for pagesbaseArticleConverter- Base converter for articles (full content)baseArticleLinkConverter- Lightweight converter for article list/browse UIs. ReturnsIContentfulArticleLink. Resolvessubtitle,visuals,tags,date,author,description,summary(rich text), andfeaturedImagewithout fetching full article content. Used internally bycontentfulAllArticleLinks.baseComponentConverter- Base converter for componentsbaseHtmlComponentConverter- Base converter forhtmlComponententries (IBaseHtmlComponent)baseCollectionConverter- Base converter for collections
Revalidation Functions
revalidateTags- Revalidates multiple Next.js cache tagsrevalidateSingleTag- Revalidates a single cache tagcreateRevalidationHandler- Creates Next.js API route handler for webhook revalidationgetCacheTags- Gets cache tags based on content type and preview mode
Error Types
ContentfulError- Base error class for Contentful API errorsRateLimitError- Error for rate limit exceededEntryNotFoundError- Error for entry not foundAuthenticationError- Error for authentication failuresValidationError- Error for validation failures
Utility Functions
isRetryableError- Check if an error is retryablewithRetry- Retry function with exponential backoffRateLimiter- Rate limiter for controlling request rates
For detailed JSDoc documentation on all exports, see the TypeScript declaration files (.d.ts) in the package.
License
MIT
Repository
https://github.com/Something-Else-Studio/se-core-product
