How Ghost Renders Themes: The Complete Frontend Rendering Pipeline Explained

Ghost transforms database entries into visitor-facing HTML through a five-stage pipeline that coordinates routing, data fetching, Handlebars compilation, and asset optimization.

The Ghost CMS front-end rendering system lives entirely within the ghost/core/core/frontend workspace of the TryGhost/Ghost repository. When a visitor requests a URL, the platform executes a deterministic sequence that moves from route resolution through template execution, ultimately delivering optimized HTML via Express.

The Five-Stage Frontend Rendering Pipeline

Ghost implements a classic "data → router → template → HTML" architecture. Each stage is orchestrated by specific services in the frontend workspace.

Stage 1: URL Routing and Route Resolution

The process begins in router-manager.js, where the Router Manager receives the incoming request and delegates to the Router Registry (registry.js).

The registry matches the URL path against built-in routers—including static pages, post collections, taxonomy routes, and RSS feeds—and returns a route object. This object specifies two critical pieces of information:

  • The entry type to render (post, page, tag, or author)
  • The template name that should handle the rendering
// core/frontend/services/routing/router-manager.js (simplified)
const routerManager = async (req, res) => {
    const route = await routerRegistry.match(req.path);   // 1️⃣ routing
    const data = await fetchData(route);                  // 2️⃣ data fetching
    const html = await themeEngine.render(route.template, data); // 3️⃣+4️⃣ rendering
    res.send(html);
};

Stage 2: Data Fetching and Context Assembly

Once the route is identified, the system invokes fetch-data.js to assemble the template context. This service queries the database to retrieve the requested entry, resolves relational models such as authors and tags, and injects auxiliary data including site settings, pagination metadata, and navigation structures.

The output is a plain JavaScript object that contains all variables available to the template during rendering.

Stage 3: Theme Engine Initialization

The Theme Engine (engine.js) prepares the rendering environment by loading the active theme from the filesystem (active.js). It merges the theme's default configuration (config/defaults.json) with any custom settings and registers both built-in and custom Handlebars helpers via helpers/handlebars.js.

This initialization ensures that the template environment includes Ghost-specific helpers like {{#foreach}}, {{img_url}}, and user-defined extensions before compilation begins.

Stage 4: Handlebars Compilation and Execution

The actual template processing occurs in handlebars/template.js and renderer.js. The Template Loader compiles .hbs files on first use and caches the resulting function for subsequent requests, eliminating redundant file system operations.

The Renderer then executes the compiled template, passing the data context assembled in Stage 2. Handlebars processes the template logic, partials, and helpers to generate the raw HTML string.

Stage 5: Asset Optimization and Localization

Before the response is returned, two final transformations occur:

  1. Asset Minification: The pipeline runs assets-minification/index.js to inline or hash CSS and JavaScript assets referenced by the template, ensuring optimal delivery.
  2. Internationalization: If i18n is enabled, i18next/theme-i18n.js performs a second pass that replaces {{t "key"}} placeholders with translated strings from the active locale.

The final HTML string is then wrapped in a response object (format-response.js) and sent through the Express server (core/server/web/express.js).

Extending the Pipeline with Custom Helpers

Ghost allows themes to extend the rendering pipeline through custom Handlebars helpers. When a theme is activated, Ghost automatically loads .ghost/helpers.js (or the path specified in the theme configuration).

// themes/my-theme/.ghost/helpers.js
module.exports = function (hbs) {
    hbs.registerHelper('readTime', function (readingTime) {
        return `${Math.round(readingTime)} min read`;
    });
};

Once registered, templates can invoke the helper directly: {{readTime reading_time}}. This integration point allows developers to inject custom logic without modifying core Ghost files.

Summary

Ghost's frontend rendering pipeline follows a strict, extensible architecture:

  • Routing: router-manager.js and registry.js match URLs to entry types and templates
  • Data: fetch-data.js assembles the template context from database entries and relations
  • Theme Setup: engine.js initializes the active theme and registers helpers
  • Rendering: template.js compiles and caches Handlebars files; renderer.js executes them
  • Optimization: Asset minification and i18next translation finalize the HTML output

This separation of concerns allows developers to customize themes, add dynamic helpers, or modify routing behavior while maintaining the underlying pipeline integrity.

Frequently Asked Questions

What template engine does Ghost use for theme rendering?

Ghost uses Handlebars as its exclusive server-side template engine. The system relies on the handlebars npm package, compiled and cached via core/frontend/services/theme-engine/handlebars/template.js. Ghost does not currently support alternative template engines like EJS or Pug without modifying core source files.

At what point does Ghost cache compiled templates?

Template compilation and caching occur during Stage 4 of the pipeline. The first time a specific .hbs file is requested, handlebars/template.js compiles it into a JavaScript function and stores it in memory. Subsequent requests for the same template reuse the cached function, significantly improving response times for high-traffic sites.

How can I inspect the rendered output of the Ghost frontend?

You can verify the rendering pipeline output using a standard HTTP request. When you request a post or page, the pipeline executes completely and returns the final HTML:

curl -s https://my-blog.com/my-post/ | grep '<article'

This returns the Handlebars-generated HTML that was processed through all five stages, including asset minification and localization.

Where does Ghost handle theme internationalization?

Theme i18n occurs in the final stage of the pipeline via i18next/theme-i18n.js. After Handlebars generates the initial HTML, the system scans for {{t "key"}} helpers and replaces them with strings from the theme's locales/ directory. This happens post-rendering but before the HTTP response is sent, ensuring translated content reaches the visitor without affecting template compilation caching.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →