How to create two (or more) blogs from a single Ghost installation
For Cove's marketing website (this site you're on!), I wanted to use a single Ghost installation to power three different "sites":
- the marketing site, which explains the product
- a blog containing Ghost-related articles
- an area for self-serve help and support documentation
- a second blog of product updates
I decided having a single Ghost site would be the best option, using it as a centralised place to contain all posts and pages together. I didn't want to set up four different sites and have to handle domain names, installing sites, and duplicating design/styling.
Using Ghost routes
it's reasonably easy to serve four different “sites” from the same installation.
The desired setup
cove.chat
would be the main product homepage- Pages like
cove.chat/page/
would let me add pages to the marketing site - The blog root would be
cove.chat/blog/
, with post URLs likecove.chat/blog/post-title/
- The help docs would be at
cove.chat/help/
, with post URLs likecove.chat/help/post-title/
- A simple Changelog page listing quick product updates would be at
cove.chat/changelog/
Updating routes.yaml
To get the desired split of content between the three collections of posts (and to push posts down a level away from the homepage), I edited the routes.yaml
file.
To work on this file, you need to download the current version from the Labs section of your Ghost admin and open it in a text editor.
Using Ghost's documentation plus some trial and error, this is how the file ended up:
routes:
/:
data: page.home
template: home
collections:
/help/:
permalink: /help/{slug}/
filter: tag:hash-help+tag:-hash-changelog
template: index-help
/changelog/:
permalink: /changelog/{slug}/
filter: tag:hash-changelog+tag:-hash-help
template: index-changelog
/blog/:
permalink: /blog/{slug}/
filter: tag:-hash-help+tag:-hash-changelog
template: index
taxonomies:
tag: /tag/{slug}/
author: /author/{slug}/
The homepage is a page I created with a URL of /home/
. Using the data
attribute, I can tell Ghost to bring in the data from that page. I specified to use the template home.hbs
for this page.
To split posts between the blog, help docs and changelog, I used the collections
section of the file. What this does is tell Ghost to use the URLs /help/
, /changelog/
and /blog/
as the bases for each collection and pull in different posts for each plus use different templates for the post list (index) view.
The filter
specifies which posts to bring in to each area of the site. All I have to do is tag any help posts with the internal tag #help
and any changelog posts with #changelog
; every other post is shown in the blog.
This system uses basic Ghost filtering. tag:hash-changelog+tag:-hash-help
means “pull in all posts with the tag hash-changelog
but not posts tagged with hash-help
” (note the -
before hash-help
). I used internal tags for these collections, which start with the #
character. Internal tags are never shown in Ghost themes and can be used to organise posts behind the scenes.
Something to note here is that I had to put the help and changelog sections above the blog section in the file to get every other post to go into the blog by default (I don't want to have to manually add a #blog
tag to every blog post).
I left the taxonomies
section of the file the same.
Now if users visit cove.chat/help/, they will see a section of only help posts, and at cove.chat/changelog/, there is a list of changelog posts. cove.chat/blog/ shows all other posts.
Different layouts
I mentioned briefly above about using different templates for each collection of posts. I wanted different designs for the blog, changelog and help docs, so I built out three slightly different "index" template files called index.hbs
, index-changelog.hbs
and index-help.hbs
(these are all in the theme's root folder).
I also created different partials for each type of post; on the blog, I want a featured image, title and date, whereas the help docs only shows the post title.
index.hbs
{{!< default}}
{{> "site-nav"}}
<div class="container">
<h1 class="font-heading">Blog</h1>
<main class="main-content">
{{#foreach posts}}
{{> "post-card-blog"}}
{{/foreach}}
{{pagination}}
</main>
partials/post-card-blog.hbs
<article class="post-card--blog {{#unless feature_image}}no-image{{else}}{{/unless}}">
{{#if feature_image}}
<a class="b0" href="{{url}}">
{{!-- This is a responsive image, it loads different sizes depending on device
https://medium.freecodecamp.org/a-guide-to-responsive-images-with-ready-to-use-templates-c400bd65c433 --}}
<img class="post-card-image mb1"
srcset="{{img_url feature_image size="s"}} 300w,
{{img_url feature_image size="m"}} 600w,
{{img_url feature_image size="l"}} 1000w,
{{img_url feature_image size="xl"}} 2000w"
sizes="(max-width: 1000px) 400px, 700px"
src="{{img_url feature_image size="m"}}"
alt="{{title}}"
/>
</a>
{{/if}}
<div>
<time datetime="{{date format="YYYY-MM-DD"}}">{{date format="D MMM YYYY"}}</time>
•
{{#if tags}}
{{#foreach tags}}
<a href="{{url}}">{{name}}</a>
{{/foreach}}
{{/if}}
<a href="{{url}}">
<h1 class="mv1 lh1 font-special">{{title}}</h1>
<section>
{{excerpt words="44"}}
</section>
</a>
</div>
</article>
index-changelog.hbs
{{!< default}}
{{> "site-nav"}}
<div class="container">
<h1 class="font-heading">Changelog</h1>
<main class="main-content">
{{#foreach posts}}
{{> "post-card-changelog"}}
{{/foreach}}
{{pagination}}
</main>
post-card-changelog.hbs
This is a super simple template that displays the full post in the list page. As these posts will be at most a couple of paragraphs, it makes sense to show the whole post.
<article class="post-card--changelog">
<div>
{{#if tags}}
{{#foreach tags}}
<span class="tag">{{name}}</span>
{{/foreach}}
{{/if}}
<time datetime="{{date format="YYYY-MM-DD"}}">{{date format="D MMM YYYY"}}</time>
<h1 class="mt0 mb0 changelog-title font-heading">{{title}}</h1>
{{content}}
</div>
</article>
index-help.hbs
The help docs page has custom lists of posts. I use tags (like Memberships
and Billing
) to categorise all posts within the help section and then simply add a header and a {{#get}}
block to get the related posts. Only the title is shown for each post.
{{!< default}}
{{> "site-nav"}}
<div class="container">
<h1 class="font-heading">Support & Help</h1>
<main class="main-content">
<div>
<h2 class="font-heading mb0">Setup & installation</h2>
<p class="fssmall color--grey mt0">How to get started with Cove and install it in your Ghost site.</p>
{{#get "posts" filter="tags:setup"}}
{{#foreach posts}}
{{> "post-card-help"}}
{{/foreach}}
{{/get}}
</div>
<div class="mt3">
<h2 class="font-heading mb0">Memberships in Ghost</h2>
<p class="fssmall color--grey mt0">How to get Members set up in Ghost.</p>
{{#get "posts" filter="tags:memberships"}}
{{#foreach posts}}
{{> "post-card-help"}}
{{/foreach}}
{{/get}}
</div>
<div class="mt3">
<h2 class="font-heading mb0">Managing members</h2>
<p class="fssmall color--grey mt0">How to manage members in Cove.</p>
{{#get "posts" filter="tags:members"}}
{{#foreach posts}}
{{> "post-card-help"}}
{{/foreach}}
{{/get}}
</div>
<div class="mt3">
<h2 class="font-heading mb0">Comments</h2>
<p class="fssmall color--grey mt0">How to manage and moderate your comments in Cove.</p>
{{#get "posts" filter="tags:comments"}}
{{#foreach posts}}
{{> "post-card-help"}}
{{/foreach}}
{{/get}}
</div>
<div class="mt3">
<h2 class="font-heading mb0">Billing</h2>
<p class="fssmall color--grey mt0">Information about pricing and the billing system.</p>
{{#get "posts" filter="tags:billing"}}
{{#foreach posts}}
{{> "post-card-help"}}
{{/foreach}}
{{/get}}
</div>
</main>
post-card-help.hbs
<div>
<a class="post-card-title" href="{{url}}">
{{title}}
</a>
</div>
And there you have it! Cove.chat is now split into four “sites”, powered by different collections of posts and a custom homepage template.