@se-studio/markdown-renderer
v1.8.18
Published
Markdown renderer for Contentful content
Downloads
7,600
Readme
@se-studio/markdown-renderer
Utilities for converting Contentful content into Markdown format. This package provides tools to export pages, articles, and custom types from Contentful and convert them into structured Markdown, suitable for LLM consumption or static site generation.
Features
- Content Export: Fetch content from Contentful (Pages, Articles, Custom Types) with full context.
- Markdown Conversion: Convert structured Contentful data into clean, readable Markdown.
- Rich Text Support: Automatically converts Contentful Rich Text fields to Markdown.
- Component, Collection & External Handling: Recursively processes nested components, collections, and external components (forms, iframes, live widgets).
- HTML components:
IBaseHtmlComponententries contributemarkdownContentto exports whenexcludeFromSearchis false;isHeroblocks are ordered first (same asCmsContent). - Frontmatter Generation: meaningful YAML frontmatter for exported content.
Installation
pnpm add @se-studio/markdown-rendererUsage
Basic Export & Conversion
import { createContentfulClient } from '@se-studio/contentful-rest-api';
import { MarkdownExporter, MarkdownConverter } from '@se-studio/markdown-renderer';
// 1. Setup Client and Exporter
const config = {
spaceId: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
environment: 'master'
};
const exporter = new MarkdownExporter(config);
const converter = new MarkdownConverter();
// 2. Fetch Content
// Supported types: 'page', 'article', 'blogPost', 'customType'
const contentData = await exporter.fetchContent('page', 'home');
if (contentData) {
// 3. Convert to Markdown
const markdown = converter.convert(contentData, {
contentContext: contentData.context,
config: config
});
console.log(markdown);
}API Reference
MarkdownExporter
Handles fetching data from Contentful and preparing the context.
constructor(config: ContentfulConfig, preview?: boolean)
config: Contentful configuration object.preview: Boolean to enable Preview API (default:false).
fetchContent(type, slug, params?)
Fetches content by slug and returns a ContentData object.
type:'page' | 'article' | 'blogPost' | 'customType'slug: The slug of the entry.params: Optional parameters (e.g.,{ articleType: 'blog' }).
For search indexing, prefer one fetchContent call per entry rather than batching many entry IDs in a single getEntries request with deep include — batched responses can truncate includes silently. See Contentful indexing hazards in the @se-studio/search package README.
MarkdownConverter
Handles the transformation of ContentData into a Markdown string.
convert(contentData, context)
contentData: The data object returned byMarkdownExporter.context: Context object containingcontentContextandconfig.
resolveMarkdownParams and route options
When building a markdown API route (e.g. /api/markdown/[...params]), use resolveMarkdownParams(slugParams, options) to map path segments to content type and slug. The resolver uses canonical first segments: articles, tags, people. Any other first segment is treated as a page (full path as slug). Path mapping from public URLs (e.g. /learning-hub/blog) to these canonical segments is the responsibility of markdown rewrites in each app's next.config.
Options:
- articlesBaseIsPage: When
true, a single segmentarticlesresolves as a page with slug"articles". - enablePrimaryTagPartOfSlug: When
true, article paths include a primary tag segment:articles/:type/:tag/:slug(or more segments whenarticleTypeSlugsis set). - articleTypeSlugs: Article type slugs in Contentful that span multiple URL segments after
articles/. Pass slugs longest-first (e.g.['resources/news', 'resources/publications']before single-segment types). The resolver matches the first configured prefix; unmatched paths return 404. In development, unsorted lists throw. Example path:/api/markdown/articles/resources/news/:tag/:slug/. - peopleCustomType: Contentful custom type for person entries (default
'people'). Use e.g.'team'when your app uses a different content type.
Custom hero converters
Built-in Article hero export includes the article title as a body heading (component.heading ?? article.title). Apps with custom hero components (e.g. Publications Hero, News Hero) or site-specific title logic should register a markdownConverter on the component registration and pass customConverters to MarkdownConverter.convert and search indexing.
Use buildMarkdownConverters from @se-studio/core-ui to collect converters from registration lists:
import { buildMarkdownConverters } from '@se-studio/core-ui';
import {
componentRegistrationsList,
collectionRegistrationsList,
externalComponentRegistrationsList,
} from '@/lib/registrations';
export const markdownCustomConverters = buildMarkdownConverters(
componentRegistrationsList,
collectionRegistrationsList,
externalComponentRegistrationsList,
);External components (type: 'External component') use the same map, keyed by externalComponentType. Without a custom converter they export heading/copy/links plus common data keys (src, url, formId) — never raw JSON.
Live widgets (tables, deal shelves) stay sync in the converter: prefetch with hydrateExternalCache (walks contents, nested collections, and RTF embeds), then read context.externalCache from the type's markdownConverter. Pass hydrators and cache in the markdown route, search indexer, and reading-time path.
For custom converters, compose helpers from markdownHeroUtils:
import {
getArticlePageContext,
renderMarkdownHeading,
renderMarkdownVisual,
resolveArticleHeading,
} from '@se-studio/markdown-renderer';Pass customConverters in both the markdown API route and search rebuild/index pipelines.
buildMarkdownCanonicalUrl
After MarkdownExporter.fetchContent, set the markdown response Link: rel="canonical" header from the same public URL logic as HTML metadata and frontmatter canonical_url (via urlCalculators). Do not build the canonical from raw slugParams — internal API paths include segments such as articles/ that are not public when article rewrites map to /api/markdown/articles/....
import { buildMarkdownCanonicalUrl } from '@se-studio/markdown-renderer';
const canonicalUrl = buildMarkdownCanonicalUrl(contentData, {
canonicalBaseUrl: baseUrl,
urlCalculators: converterContext.urlCalculators,
});Types
interface ContentData {
contentType: 'page' | 'article' | 'customType';
data: IBasePage | IBaseArticle | IBaseCustomType;
context: IContentContext;
}
interface MarkdownConverterContext {
contentContext: IContentContext;
config: ContentfulConfig;
}License
MIT
