# How Ghost Renders Themes: The Complete Frontend Rendering Pipeline Explained

> Discover how Ghost renders themes with its five-stage frontend pipeline. Learn about routing, data fetching, Handlebars compilation, and asset optimization for efficient theme delivery.

- Repository: [Ghost/Ghost](https://github.com/TryGhost/Ghost)
- Tags: internals
- Published: 2026-05-18

---

**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](https://github.com/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`](https://github.com/TryGhost/Ghost/blob/main/router-manager.js), where the **Router Manager** receives the incoming request and delegates to the **Router Registry** ([`registry.js`](https://github.com/TryGhost/Ghost/blob/main/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

```javascript
// 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`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/engine.js)) prepares the rendering environment by loading the active theme from the filesystem ([`active.js`](https://github.com/TryGhost/Ghost/blob/main/active.js)). It merges the theme's default configuration ([`config/defaults.json`](https://github.com/TryGhost/Ghost/blob/main/config/defaults.json)) with any custom settings and registers both built-in and custom Handlebars helpers via [`helpers/handlebars.js`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/handlebars/template.js) and [`renderer.js`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/format-response.js)) and sent through the Express server ([`core/server/web/express.js`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/.ghost/helpers.js) (or the path specified in the theme configuration).

```javascript
// 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`](https://github.com/TryGhost/Ghost/blob/main/router-manager.js) and [`registry.js`](https://github.com/TryGhost/Ghost/blob/main/registry.js) match URLs to entry types and templates
- **Data**: [`fetch-data.js`](https://github.com/TryGhost/Ghost/blob/main/fetch-data.js) assembles the template context from database entries and relations
- **Theme Setup**: [`engine.js`](https://github.com/TryGhost/Ghost/blob/main/engine.js) initializes the active theme and registers helpers
- **Rendering**: [`template.js`](https://github.com/TryGhost/Ghost/blob/main/template.js) compiles and caches Handlebars files; [`renderer.js`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/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:

```bash
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`](https://github.com/TryGhost/Ghost/blob/main/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.