Ghost
Integrate Ghost as a powerful headless CMS or a full-featured publishing platform.
You are a web developer specializing in content-rich applications, expertly integrating Ghost as a headless CMS or a complete publishing solution. You leverage Ghost's robust Content API to power dynamic frontends, delivering fast, engaging user experiences driven by its editorial simplicity and performance. ## Key Points * **Implement Robust Error Handling:** Always wrap API calls in `try...catch` blocks to gracefully handle network issues, invalid keys, or content not found. * **Use `include` Parameter Wisely:** Include related resources (tags, authors) in a single request using the `include` parameter to avoid N+1 problems and minimize API roundtrips. * **Specify `fields` for Efficiency:** Only request the data fields you actually need using the `fields` parameter to reduce payload size and improve transfer speed. ## Quick Example ```bash npm install @tryghost/content-api # OR yarn add @tryghost/content-api ```
skilldb get cms-services-skills/GhostFull skill: 255 linesGhost Integration
You are a web developer specializing in content-rich applications, expertly integrating Ghost as a headless CMS or a complete publishing solution. You leverage Ghost's robust Content API to power dynamic frontends, delivering fast, engaging user experiences driven by its editorial simplicity and performance.
Core Philosophy
Ghost is built from the ground up for professional publishing. Its core philosophy revolves around delivering a fast, focused, and flexible platform for creators. It provides a beautiful, minimal editor experience that allows content creators to publish effortlessly, focusing on their writing rather than complex UI. When you choose Ghost, you're opting for a content platform that prioritizes speed, SEO, and user experience, both for content creators and end-users.
Ghost distinguishes itself by offering both a traditional blog/CMS experience with built-in themes and a powerful headless CMS via its Content API. This dual capability makes it incredibly versatile: you can use Ghost to host your entire publication, including themes, subscriptions, and member management, or you can decouple its content engine and consume its API from any custom frontend built with frameworks like React, Vue, or Next.js. This flexibility ensures that your content strategy can evolve without being constrained by your presentation layer.
Setup
Integrating Ghost primarily involves interacting with its Content API. You'll need a running Ghost instance (either self-hosted or Ghost Pro) and your Content API Key.
First, install the official Ghost Content API client in your project:
npm install @tryghost/content-api
# OR
yarn add @tryghost/content-api
Next, initialize the client using your Ghost site's URL and Content API Key. You can find these credentials in your Ghost Admin under "Integrations" -> "Content API".
// src/lib/ghost.js (or similar utility file)
import GhostContentAPI from '@tryghost/content-api';
// Ensure these are loaded from environment variables in a production setup
const GHOST_API_URL = process.env.NEXT_PUBLIC_GHOST_API_URL;
const GHOST_CONTENT_API_KEY = process.env.NEXT_PUBLIC_GHOST_CONTENT_API_KEY;
if (!GHOST_API_URL || !GHOST_CONTENT_API_KEY) {
console.error("Ghost API URL or Key is missing. Check your environment variables.");
// Handle error appropriately, e.g., throw new Error(...)
}
const api = new GhostContentAPI({
url: GHOST_API_URL,
key: GHOST_CONTENT_API_KEY,
version: 'v5' // Use the correct API version for your Ghost instance (e.g., 'v3', 'v4', 'v5')
});
export default api;
Key Techniques
Fetching a List of Posts
Retrieve multiple posts, apply filters, and manage pagination. This is fundamental for blog listings, category pages, or archives.
// src/app/blog/page.js (Example in a Next.js App Router component)
import ghost from '../../lib/ghost';
async function getPosts() {
try {
const posts = await ghost.posts
.browse({
limit: 10, // Fetch 10 posts per page
include: 'tags,authors', // Include related tags and authors
fields: 'title,slug,custom_excerpt,feature_image,published_at', // Only fetch specified fields
filter: 'tag:hash-featured', // Example: filter by a featured internal tag
order: 'published_at DESC' // Sort by publication date
})
.fetch();
return posts;
} catch (error) {
console.error('Error fetching posts:', error);
return [];
}
}
export default async function BlogPage() {
const posts = await getPosts();
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-8">Latest Articles</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{posts.map((post) => (
<article key={post.id} className="border p-4 rounded-lg shadow">
{post.feature_image && (
<img src={post.feature_image} alt={post.title} className="w-full h-48 object-cover rounded-md mb-4" />
)}
<h2 className="text-2xl font-semibold mb-2">{post.title}</h2>
<p className="text-gray-600 text-sm mb-4">
Published on {new Date(post.published_at).toLocaleDateString()}
{post.authors && post.authors.length > 0 && ` by ${post.authors[0].name}`}
</p>
<p className="text-gray-700">{post.custom_excerpt}</p>
<a href={`/blog/${post.slug}`} className="text-blue-600 hover:underline mt-4 inline-block">Read more</a>
</article>
))}
</div>
</div>
);
}
Fetching a Single Post by Slug
Retrieve the full content of a specific post, essential for individual article pages.
// src/app/blog/[slug]/page.js (Example in a Next.js App Router component)
import ghost from '../../../lib/ghost';
import Link from 'next/link';
async function getSinglePost(slug) {
try {
const post = await ghost.posts
.read({
slug: slug
}, {
include: 'tags,authors' // Ensure related data is included
})
.fetch();
return post;
} catch (error) {
console.error(`Error fetching post with slug ${slug}:`, error);
return null;
}
}
export default async function PostPage({ params }) {
const post = await getSinglePost(params.slug);
if (!post) {
return (
<div className="container mx-auto px-4 py-8 text-center">
<h1 className="text-3xl font-bold mb-4">Post not found</h1>
<p className="text-lg">The article you are looking for does not exist or has been moved.</p>
<Link href="/blog" className="text-blue-600 hover:underline mt-4 inline-block">Back to Blog</Link>
</div>
);
}
return (
<div className="container mx-auto px-4 py-8 max-w-3xl">
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<p className="text-gray-600 text-sm mb-6">
Published on {new Date(post.published_at).toLocaleDateString()}
{post.authors && post.authors.length > 0 && ` by ${post.authors[0].name}`}
</p>
{post.feature_image && (
<img src={post.feature_image} alt={post.title} className="w-full h-64 object-cover rounded-md mb-8" />
)}
<div className="prose lg:prose-lg max-w-none" dangerouslySetInnerHTML={{ __html: post.html }}></div>
{post.tags && post.tags.length > 0 && (
<div className="mt-8">
<h3 className="text-xl font-semibold mb-2">Tags:</h3>
<div className="flex flex-wrap gap-2">
{post.tags.map(tag => (
<span key={tag.id} className="bg-gray-200 text-gray-800 px-3 py-1 rounded-full text-sm">
{tag.name}
</span>
))}
</div>
</div>
)}
<Link href="/blog" className="text-blue-600 hover:underline mt-8 inline-block">← Back to all posts</Link>
</div>
);
}
// For static site generation (e.g., Next.js)
export async function generateStaticParams() {
const posts = await ghost.posts.browse({ limit: 'all' }).fetch();
return posts.map((post) => ({
slug: post.slug,
}));
}
Working with Pages and Members
Fetch static pages for "About Us" or "Contact" sections, or interact with members-only content if your Ghost site uses memberships.
// Fetching a specific static page (e.g., 'about-us')
import ghost from '../../lib/ghost';
async function getAboutPage() {
try {
const page = await ghost.pages
.read({
slug: 'about-us' // Replace with your page slug
})
.fetch();
return page;
} catch (error) {
console.error('Error fetching about page:', error);
return null;
}
}
// Example of integrating with members (requires member-specific API key or client-side logic)
// For protected content, you'd typically use the Ghost JavaScript SDK on the client-side
// or a server-side proxy with the Admin API for more control.
// This example assumes content is publicly accessible but might be gated by your Ghost theme/config.
async function getPremiumPosts() {
try {
// This fetches all posts, you'd filter by access tier if you have Ghost Members enabled
const premiumPosts = await ghost.posts
.browse({
filter: 'visibility:paid', // Filter for posts visible only to paid members
limit: 5,
fields: 'title,slug,access'
})
.fetch();
return premiumPosts;
} catch (error) {
console.error('Error fetching premium posts:', error);
return [];
}
}
// Usage in a component:
// const aboutPage = await getAboutPage();
// const premiumContent = await getPremiumPosts();
Best Practices
- Implement Robust Error Handling: Always wrap API calls in
try...catchblocks to gracefully handle network issues, invalid keys, or content not found. - Cache API Responses: For static content like posts and pages, implement client-side (e.g., SWR, React Query) or server-side caching (e.g., Redis,
next/cache) to reduce API calls and improve performance. - Use
includeParameter Wisely: Include related resources (tags, authors) in a single request using theincludeparameter to avoid N+1 problems and minimize API roundtrips. - Specify
fieldsfor Efficiency: Only request the data fields you actually need using thefieldsparameter to reduce payload size and improve transfer speed. - Optimize Image Delivery: Ghost automatically provides optimized image URLs. Ensure your frontend client is rendering these effectively, potentially using an image optimization component (e.g., Next.js
Image). - Protect API Keys: While the Content API key is read-only and relatively safe for public use, never expose your Admin API key in client-side code. Use environment variables for all keys and ideally proxy Admin API requests through a secure backend.
- Leverage Webhooks for Real-time Updates: For applications requiring real-time content synchronization (e.g., rebuilding a static site on content changes), configure Ghost webhooks to trigger builds or updates.
Anti-Patterns
Hardcoding API Keys in Frontend. Never embed your Content API key directly in publicly accessible frontend code. While the Content API is read-only, it's a security best practice to manage it via environment variables and build processes.
Over-fetching Data. Don't request all fields (fields: 'all') if you only need a few. This sends unnecessary data over the wire, slowing down your application and increasing API usage.
Ignoring Pagination. For large datasets, always implement pagination when fetching lists of posts or pages to avoid performance issues, memory exhaustion, and to provide a better user experience.
Not Caching API Responses. Repeatedly fetching the same static content will slow down your application and consume unnecessary API limits. Implement client-side or server-side caching for stable content.
Using Admin API for Public Content. The Ghost Admin API is for managing content programmatically (creating, updating, deleting). It should never be used to display public-facing content; stick to the Content API for this purpose.
Install this skill directly: skilldb add cms-services-skills
Related Skills
Builder Io
Builder.io is a Visual Headless CMS and API that empowers developers to integrate
Caisy
caisy is a headless CMS designed for speed and scalability, empowering developers
Contentful
Build with Contentful for headless content management. Use this skill when the
Cosmic
Integrate Cosmic as your headless content management system, providing
Directus
Build with Directus for database-first content management. Use this skill
Hygraph
Build with Hygraph (formerly GraphCMS) as a GraphQL-native headless CMS.