A specter is haunting Europe. This is its theme. 👻 A Ghost theme for politicians and local chapters of the German Left Party.
| Live Demo | Download |
|---|
With my manually customized Docker image (repository: hutt/spectre-docker-compose), a website with the Spectre theme can be deployed in under 3 minutes:
# Clone the repository & change into the working directory (working directory here is "my-website.com")
git clone https://github.com/hutt/spectre-docker-compose.git my-website.com && cd my-website.com
# Copy the template for the environment variables file and adapt it to your needs
cp example.env .env
nano .env # Adjust: domain, email, password, blog title...
# Start the dependencies and bring up the containers:
docker compose up -d
# Follow the logs to see if everything worked
docker compose logs -f ghost-bootstrapGhost uses a simple templating language called Handlebars for its themes.
The main files are:
default.hbs– The parent template containing the global header/footerhome.hbs– The homepageindex.hbs– The primary template for generating a list of postspost.hbs– The template for displaying individual postspage.hbs– Used for standalone pagestag.hbs– Used for tag archives, e.g., “all posts taggednews”author.hbs– Used for author archives, e.g., “all posts by Jamie”
You can also create custom templates by adding the page slug to the template filename. For example:
page-about.hbs– Custom template for an/about/pagetag-news.hbs– Custom template for the/tag/news/archiveauthor-ines.hbs– Custom template for the/author/ines/archive
This theme supports a static homepage as well as a blog index page. To set a static homepage at /, you need to adjust the routes.yaml file.
For example (the slug of the homepage is start, so we use data: page.start):
routes:
/:
template: page
data: page.start
/sitemap/:
template: sitemap
content_type: text/html
collections:
/presse/mitteilungen/:
permalink: /presse/mitteilungen/{year}/{slug}/
template: search-pressemitteilungen
filter: 'tag:hash-pressemitteilung'
/termine/archiv/:
permalink: /termine/{year}/{slug}/
template: search-termine
filter: 'tag:hash-termin'
/blog/:
permalink: /blog/{slug}/
template: home
taxonomies:
tag: /tag/{slug}/
author: /autor_in/{slug}/
#termin) cannot appear in subsequent collections (e.g., /blog/), so /blog/ should not include a filter to exclude press releases or events.
Example: If the /blog/ collection is defined before /termine/archiv/ in the routes file, the events collection will be empty because those posts have already been assigned to /blog/.
Learn more about collections in the official Ghost documentation.
As shown in the routes.yaml example above, this theme also supports custom pages for events and press releases so they don’t appear alongside regular posts in the blog index.
Add the following lines under the collections: block in routes.yaml:
collections:
/termine/archiv/:
permalink: /termine/{year}/{slug}/
template: search-termine
filter: 'tag:hash-termin'
Create a page in the editor, name it (e.g., “Termine” with slug termine), and fill in the content. Then select the Termine template in the page settings sidebar. The page will now display the five most recent events below its main content.
To add an event, create a new post and include the event details. It’s recommended to put the event date in the post title (e.g., 31.12.2024: Silvesterfeier). Tag the post with #termin to ensure it appears only on the Events page (and the Events archive at /termine/archiv), not in the main blog index.
Add the following lines under the collections: block in routes.yaml:
collections:
/presse/mitteilungen/:
permalink: /presse/mitteilungen/{year}/{slug}/
template: search-pressemitteilungen
filter: 'tag:hash-pressemitteilung'
Create a page, name it (e.g., “Presse” with slug presse), and add content such as press contacts or photos. Then select the Presse template in the page settings sidebar. The page will now display the five most recent press releases below its main content.
To add a press release, create a new post, tag it with #pressemitteilung, and it will appear only on the Press Releases page (and /presse/mitteilungen archive), not in the main blog index.
This theme enables GDPR-compliant YouTube embeds using light-yt.js by Amit Agarwal.
-
Copy the YouTube video ID (from
https://www.youtube.com/watch?v=dQw4w9WgXcQ, the ID isdQw4w9WgXcQ). -
In the Ghost editor, add an HTML block where the video should appear.
-
Insert the following code, replacing
VideoIDwith your copied ID:<div class="youtube-player" data-id="VideoID"></div>(Example:
<div class="youtube-player" data-id="dQw4w9WgXcQ"></div>)
Save this block as a snippet in Ghost so you don’t have to retype it each time.
This theme can generate Google News–compatible sitemaps. Just define a route with the sitemap template as shown:
routes:
/sitemap/:
template: sitemap
content_type: text/html
Then submit the sitemap URL (https://your-site.com/sitemap/) in Google Search Console.
For faster load times, upload your logo in SVG format. If you don’t, Spectre will generate a logo from your site title.
Spectre themes are compiled with Gulp/PostCSS. You need Node, Yarn, and Gulp installed globally. Then run from the theme’s root:
# Install dependencies
yarn install
# Start development server
yarn dev
Any changes in /assets/css/ will automatically compile into /assets/built/. To package the theme:
# Create zip archive
yarn zip
- Autoprefixer – Automatically handles vendor prefixes for CSS features.
Spectre uses several techniques to speed up page loads: preloading key assets, bundling and minifying CSS/JS, critical inline CSS, and embedding icons as SVGs instead of icon fonts.
To improve the time to first contentful paint, Spectre uses penthouse to generate critical CSS for post, page, tag, and index templates. These styles are included via Handlebars partials (default.hbs and partials/components/inline-css.hbs). Run yarn critical to regenerate.
Spectre includes inline SVG icons in /partials/icons. Use an icon with:
{{> "icons/rss"}}
Add new icons by placing them in /partials/icons.
By default, Ghost and light-yt.js make requests to external services. While usually privacy-safe, you can avoid them entirely.
Adjust this docker-compose.yml to use hutt/spectre-docker-compose, then enable YouTube proxying with:
<script>
const YT_DATA_URL_PREFIX = "/proxy/youtube/data";
const YT_THUMBNAIL_URL_PREFIX = "/proxy/youtube/thumbnail";
</script>
Override portal, search, and comments URLs in config.[env].json or deploy a Cloudflare Worker to proxy and cache JSDelivr requests under /cdn-jsdelivr/*. Update:
{
"portal": {
"url": "/cdn-jsdelivr/npm/@tryghost/portal@~{version}/umd/portal.min.js"
},
...
}
light-yt.js fetches metadata from youtube-nocookie.com and thumbnails from i.ytimg.com. You can proxy these via an nginx cache or Cloudflare Worker at /yt-proxy/* and configure:
<script>
const YT_DATA_URL_PREFIX = "/yt-proxy/data";
const YT_THUMBNAIL_URL_PREFIX = "/yt-proxy/thumbnail";
</script>
Copyright (c) 2013–2023 Ghost Foundation; 2023–2025 Jannis Hutt.
Based on Source by Ghost Foundation. Licensed under the MIT License.

