Skills contentful
install
source · Clone the upstream repo
git clone https://github.com/TerminalSkills/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/TerminalSkills/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/contentful" ~/.claude/skills/terminalskills-skills-contentful && rm -rf "$T"
manifest:
skills/contentful/SKILL.mdsafety · automated scan (low risk)
This is a pattern-based risk scan, not a security review. Our crawler flagged:
- references .env files
Always read a skill's source content before installing. Patterns alone don't mean the skill is malicious — but they warrant attention.
source content
Contentful — Enterprise Headless CMS
You are an expert in Contentful, the API-first content platform for enterprise teams. You help developers integrate Contentful's Content Delivery API (CDN-backed, read), Content Management API (write), and Content Preview API (draft content) into websites and apps — using typed content models, localization, rich text rendering, image transformations, and webhooks for build triggers.
Core Capabilities
Content Delivery API
// src/lib/contentful.ts — Contentful client setup import { createClient, type Entry, type Asset } from "contentful"; const client = createClient({ space: process.env.CONTENTFUL_SPACE_ID!, accessToken: process.env.CONTENTFUL_DELIVERY_TOKEN!, // For draft preview: // accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN, // host: "preview.contentful.com", }); // TypeScript interfaces matching content model interface BlogPostFields { title: string; slug: string; excerpt: string; body: Document; // Rich Text featuredImage: Asset; author: Entry<AuthorFields>; tags: string[]; publishDate: string; } // Fetch entries with type safety async function getBlogPosts(limit = 10): Promise<Entry<BlogPostFields>[]> { const response = await client.getEntries<BlogPostFields>({ content_type: "blogPost", order: ["-fields.publishDate"], limit, include: 2, // Resolve 2 levels of linked entries "fields.slug[exists]": true, // Only entries with slug }); return response.items; } async function getBlogPostBySlug(slug: string) { const response = await client.getEntries<BlogPostFields>({ content_type: "blogPost", "fields.slug": slug, include: 3, limit: 1, }); return response.items[0] || null; }
Rich Text Rendering
// src/components/RichTextRenderer.tsx — Render Contentful rich text import { documentToReactComponents, Options } from "@contentful/rich-text-react-renderer"; import { BLOCKS, INLINES, Document } from "@contentful/rich-text-types"; import Image from "next/image"; const renderOptions: Options = { renderNode: { [BLOCKS.EMBEDDED_ASSET]: (node) => { const { title, file } = node.data.target.fields; return ( <Image src={`https:${file.url}`} alt={title} width={file.details.image.width} height={file.details.image.height} className="rounded-lg my-6" /> ); }, [BLOCKS.EMBEDDED_ENTRY]: (node) => { const entry = node.data.target; if (entry.sys.contentType.sys.id === "codeBlock") { return ( <pre className="bg-gray-900 p-4 rounded-lg overflow-x-auto"> <code className={`language-${entry.fields.language}`}> {entry.fields.code} </code> </pre> ); } return null; }, [INLINES.HYPERLINK]: (node, children) => ( <a href={node.data.uri} className="text-blue-600 underline" target="_blank" rel="noopener"> {children} </a> ), }, }; export function RichText({ document }: { document: Document }) { return <div className="prose max-w-none">{documentToReactComponents(document, renderOptions)}</div>; }
Image Transformations
// Contentful Images API — resize, crop, format on the fly function contentfulImageUrl(asset: Asset, options?: { width?: number; height?: number; quality?: number; format?: "webp" | "avif" | "jpg" | "png"; fit?: "pad" | "fill" | "scale" | "crop" | "thumb"; focus?: "face" | "center" | "top" | "bottom"; }) { const url = new URL(`https:${asset.fields.file.url}`); if (options?.width) url.searchParams.set("w", String(options.width)); if (options?.height) url.searchParams.set("h", String(options.height)); if (options?.quality) url.searchParams.set("q", String(options.quality)); if (options?.format) url.searchParams.set("fm", options.format); if (options?.fit) url.searchParams.set("fit", options.fit); if (options?.focus) url.searchParams.set("f", options.focus); return url.toString(); } // Usage: auto-optimize for web <Image src={contentfulImageUrl(post.fields.featuredImage, { width: 800, format: "webp", quality: 80, })} alt={post.fields.title} />
Installation
npm install contentful # Delivery SDK npm install @contentful/rich-text-react-renderer # Rich text → React npm install contentful-management # Management API (write)
Best Practices
- Delivery API for reads — Use CDN-backed Delivery API for production; Preview API only for draft preview
- Type generation — Use
to generate TypeScript types from your content modelcontentful-typescript-codegen - Rich text renderer — Always use
with custom renderers for embedded assets and entries@contentful/rich-text-react-renderer - Image API — Transform images on the fly with URL params; serve WebP with quality 80 for 70% size reduction
- Webhooks for builds — Configure webhooks to trigger Vercel/Netlify builds on publish; ISR for immediate updates
- Localization — Set up locales in Contentful; fetch with
orlocale: "de"
for all localeslocale: "*" - Include depth — Set
to resolve linked entries; avoids N+1 queries for related contentinclude: 2-3 - Environment branching — Use Contentful Environments (like Git branches) for content model changes; merge to master when ready