Ghost CMS Blog Integration

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Ghost CMS Integration for Blog

Ghost — a specialized CMS for publications. Card editor, native support for paid subscriptions, built-in mailing, SEO out of the box. Don't use Ghost as a universal CMS — it's a tool for media products and blogs where the main content is articles.

Two integration scenarios: Ghost as a headless backend with a custom frontend via Content API, or Ghost with its own themes on Handlebars.

Content API

Ghost provides a public Content API and a private Admin API. For reading on the frontend, you only need the Content API:

npm install @tryghost/content-api
// lib/ghost.ts
import GhostContentAPI from '@tryghost/content-api'

export const ghost = new GhostContentAPI({
  url: process.env.GHOST_URL!,        // https://blog.example.com
  key: process.env.GHOST_CONTENT_KEY!, // from Ghost > Integrations settings
  version: 'v5.0',
})

// All posts with tags and authors
export async function getPosts(options = {}) {
  return ghost.posts.browse({
    limit: 'all',
    include: ['tags', 'authors'],
    filter: 'visibility:public',
    order: 'published_at DESC',
    ...options,
  })
}

// Single post by slug
export async function getPostBySlug(slug: string) {
  return ghost.posts.read({ slug }, { include: ['tags', 'authors'] })
}

// Pages (static, not posts)
export async function getPage(slug: string) {
  return ghost.pages.read({ slug })
}

Next.js 14 Integration

// app/blog/page.tsx
import { getPosts } from '@/lib/ghost'

export const revalidate = 3600 // ISR — update once per hour

export default async function BlogPage() {
  const posts = await getPosts({ limit: 20 })

  return (
    <div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
      {posts.map(post => (
        <article key={post.id}>
          {post.feature_image && (
            <img src={post.feature_image} alt={post.feature_image_alt || post.title} />
          )}
          <h2><a href={`/blog/${post.slug}`}>{post.title}</a></h2>
          <p>{post.excerpt}</p>
          <time>{new Date(post.published_at!).toLocaleDateString('uk-UA')}</time>
        </article>
      ))}
    </div>
  )
}
// app/blog/[slug]/page.tsx
import { getPosts, getPostBySlug } from '@/lib/ghost'

export async function generateStaticParams() {
  const posts = await getPosts({ fields: ['slug'] })
  return posts.map(p => ({ slug: p.slug }))
}

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug).catch(() => notFound())

  return (
    <article>
      <h1>{post.title}</h1>
      {/* Ghost returns ready-made HTML */}
      <div
        className="prose prose-lg max-w-none"
        dangerouslySetInnerHTML={{ __html: post.html! }}
      />
    </article>
  )
}

Ghost returns already rendered HTML. For custom processing, there's Lexical JSON (field lexical) — Ghost 6's internal editor format.

Webhook for Revalidation

// app/api/ghost-webhook/route.ts
import { revalidatePath, revalidateTag } from 'next/cache'

export async function POST(request: Request) {
  const secret = request.headers.get('x-ghost-signature')
  // HMAC signature check
  const body = await request.json()

  // Ghost sends post.published, post.updated, etc. events
  const { event } = body
  if (['post.published', 'post.updated', 'post.deleted'].includes(event)) {
    revalidateTag('ghost-posts')
    revalidatePath('/blog')
  }

  return new Response('OK')
}

In Ghost settings: Settings → Integrations → Custom Integrations → Add webhook.

Admin API — Programmatic Content Creation

import GhostAdminAPI from '@tryghost/admin-api'

const ghostAdmin = new GhostAdminAPI({
  url: process.env.GHOST_URL!,
  key: process.env.GHOST_ADMIN_KEY!, // format: id:secret
  version: 'v5.0',
})

// Create a post
await ghostAdmin.posts.add({
  title: 'Automatically created post',
  html: '<p>Content</p>',
  status: 'published',
  tags: [{ name: 'automation' }],
  authors: [{ email: '[email protected]' }],
})

// Upload an image
const image = await ghostAdmin.images.upload({ file: './cover.jpg' })

This is used when migrating content from other CMSes or automatic publishing.

Self-hosted Ghost on Docker

# docker-compose.yml
services:
  ghost:
    image: ghost:5-alpine
    restart: always
    environment:
      url: https://blog.example.com
      database__client: mysql
      database__connection__host: db
      database__connection__user: ghost
      database__connection__password: ${DB_PASSWORD}
      database__connection__database: ghost
      mail__transport: SMTP
      mail__options__service: Mailgun
      mail__options__auth__user: ${MAILGUN_USER}
      mail__options__auth__pass: ${MAILGUN_PASS}
    volumes:
      - ghost-content:/var/lib/ghost/content
    ports:
      - "2368:2368"

  db:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ghost
      MYSQL_USER: ghost
      MYSQL_PASSWORD: ${DB_PASSWORD}
    volumes:
      - mysql-data:/var/lib/mysql

Ghost supports SQLite (for small blogs) and MySQL. PostgreSQL is not officially supported.

Handlebars Themes

If headless isn't needed — Ghost works with its own themes:

{{!-- index.hbs --}}
{{#foreach posts}}
  <article class="post">
    <h2><a href="{{url}}">{{title}}</a></h2>
    {{#if feature_image}}
      <img src="{{img_url feature_image size="m"}}" alt="{{feature_image_alt}}">
    {{/if}}
    <p>{{excerpt words="30"}}</p>
    {{#foreach tags}}
      <a href="{{url}}" class="tag">{{name}}</a>
    {{/foreach}}
  </article>
{{/foreach}}
{{pagination}}

For theme development: ghost-cli + local Ghost, hot reload via gscan.

Membership and Subscriptions

Ghost 5+ includes built-in monetization through Stripe. Config in config.production.json:

{
  "members": {
    "enabled": true,
    "trackSources": true
  },
  "stripeDirect": true
}

Paid content is marked in the editor as Members only or Paid members only — Ghost controls access itself.

Timeline

Headless integration with Next.js, webhook, ISR: 2–3 days. Self-hosted deployment + custom theme + subscription setup: 5–8 days.