@se-studio/contentful-rest-api
v1.0.107
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;
}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 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 (metadata only)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 componentsbaseCollectionConverter- 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
