The basic purpose of a router is to match URL patterns and perform actions as a result, such as showing you a page or submitting a form request. Routing is at the heart of your web application and drives the user interface.

It comes pre-configured and chances are you do not need to change anything. However, if you need to customize your site layout, a good understanding of your site's routing system, and the URL patterns it uses, is of great advantage.

URL patterns are indexed by search engines and it therefore comes at no surprise that routing is an integral part of search engine optimization. This is another reason why you may take an interest in this topic.

Site layout

Before looking into how to customize the routing system, let's first look at the standard site layout as it is pre-configured. The Ghost CMS comes with an extremely simple site structure, centred around posts.

A post is amended by authors and tags, which can be viewed as properties of of posts. Finally, there are pages which are almost identical to posts. With this data model in mind, the standard site structure is quite easy to understand:

/   : index of all articles

/post1/   : first article
/post2/   : second article

/tag/tag1/   : all articles tagged with tag1
/tag/tag2/   : all articles tagged with tag2

/author/author1/   : all articles with author1
/author/author2/   : all articles with author2

In reality, this simple picture is a bit more involved due to pagination:

/          : index of articles, page 1
/page/2/   : index of articles, page 2
/page/3/   : index of articles, page 3

/post1/     : first article
/post2/     : second article

/tag/tag1/     : articles tagged with tag1, page 1
/tag/tag1/2/   : articles tagged with tag1, page 2
/tag/tag1/3/   : articles tagged with tag1, page 3
/tag/tag2/     : articles tagged with tag2, page 1
/tag/tag2/2/   : articles tagged with tag2, page 2

/author/author1/     : articles with author1, page 1
/author/author1/2/   : articles with author1, page 2
/author/author1/3/   : articles with author1, page 3
/author/author2/     : articles with author2, page 1
/author/author2/2/   : articles with author2, page 2
Although Jamify's Casper theme uses infinite scroll, paginated pages are still generated, so they can be indexed by search engines!

Instructing the Router

With a growing number of posts, authors and tags, you can imagine how the above list will grow. It would be very inefficient to lay out every single route to the router.

That's where patterns come to the rescue and it's these patterns that you need to instruct the router with. Here is a first glimpse into such an instruction file:

# ./content/settings/routes.yaml


    permalink: /{slug}/
    template: index

  tag: /tag/{slug}/
  author: /author/{slug}/

I'm sure you can recognize the patterns for tags and authors as well as the ones for posts under the permalink key. I will discuss this file in more depth later, for now simply remember that routes.yaml is one place where the router can be configured.

Add a landing page

Many people run a blog as part of a larger site. In this case, you typically have a landing page that is different from the blog index page.

How do you integrate a landing page at the root URL, given that your Jamify site already shows an index page under the same address? Routes must be unique, obviously the Jamify index page should be moved to some other URL.

But wait..., if your blog index page is moved, for example, from to, what about all other routes? Wouldn't it make sense to also move the posts, e.g. from to

Although not required, it is highly recommended to leave the original site structure intact and move all routes one level down, so the root path for the Jamify blog content changes from / to /blog. This also prevents naming conflicts with other pages that you might add to your new root later.

Start a new project

Let's start with a new Jamify project and go through the steps required to add a landing page. First, clone the Jamify starter as usual:

$ git clone jamify-routing

and change into the work directory:

$ cd jamify-routing

Import all dependencies with

[jamify-routing]$ yarn

and check that you can build...

[jamify-routing]$ yarn develop

...and access the Jamify demo page at http://localhost:8000. This address is your route domain and I refer to it with the foreslash / from here on. Play a little with this demo and take note of the URL structure when navigating through posts and pages.

Move all routes

To make room for the landing page, we want to move all existing routes one level down from / to /blog. There are multiple ways to achieve this:

  1. Change routes.yaml in your headless Ghost CMS.
  2. Use a reverse proxy in front of your site and configure the proxy to perform the routing from / to /blog.
  3. Use the basePath configuration option that Jamify provides.

For this particular use case, the basePath option is by far the easiest. Open routesConfig.js in your favourite editor and add the basePath option as follows:

// routesConfig.js
module.exports = {

    basePath: `/blog`,
    collections: [],
You can also move your site to a different path, for example to /publications or down to a deeper level, e.g. /pubs/blog.

Rebuild your project with yarn clean; yarn develop and open it in development mode, again at http://localhost:8000:

Oops, where did your page go? Indeed, after moving your site the router cannot find anything at the root index / anymore!

The Gatsby 404 development page shows a number of URLs that it can find. Note that all routes are moved under /blog now.

Go to http://localhost:8000/blog to see your blog index page again...

...and navigate through the pages to see if everything is working as expected.

Install the landing page

I'm going to install a nice Gatsby landing page developed by Kyle Gill with some small modifications. I had to localize the global css styles of the landing page, so they don't interfere with the Jamify site styles.

As the landing page is not available as a Gatsby theme, we install it as a local plugin. Make sure that you are in the work directory and clone the landing page into the plugin folder:

[jamify-routing]$ git clone plugins/landing-page

You can then add the local plugin in gatsby-config.js as the first plugin in the array:

// gatsby-config.js
plugins: [
        resolve: `./plugins/landing-page`,

Rebuild with yarn develop and view your site in the browser at http://localhost:8000. You should be greeted with the newly added landing page:

You can click on the Get Early Access button to navigate to your blog!

Finding back home

This looks all pretty cool. You moved all your blog pages under /blog and made space for the landing page that is now installed as your root page. Further more, you can reach you blog from the landing page.

There is just a tiny flaw. Once you have navigated to your blog, you cannot go back to you landing page other than using the back button of your browser. When you click Home in the navigation bar, you'll stay on your blog index page.

This may be exactly what you want, especially if you rename the menu item form Home to Blog. However, it should be possible to provide a navigation item that brings you back to the landing page.

There is a new option available in Jamify that was designed exactly for that purpose: overriding menu links. Simply add this one-liner to your siteConfig.js:

module.exports = {

    // leave all previous options untouched and add:
    overwriteGhostNavigation: [{ label: `Home`, url: `/` }],

The option overwriteGhostNavigation looks up existing navigation items by label and replaces the existing url with the one given in the configuration. That way we can override the Home link from /blog to /.

Save the file, rebuild with yarn develop and check that the Home navigation item show localhost:8000 instead of localhost:8000/blog

and brings you back home to the landing page.

Source your own content

In the next chapter you are going to make changes in the headless Ghost CMS. As you only have read access to the demo site, it's now time to source your own site content. Read the Getting started with Jamify tutorial, if you are new to sourcing in your own content.

In a nutshell, you need to create a new .ghost.json file in the work directory and copy your Content API keys in:

  "development": {
    "apiUrl": "",
    "contentApiKey": "e5e630bdbbf0a09dd0f7980c84"
  "production": {
    "apiUrl": "",
    "contentApiKey": "e5e630bdbbf0a09dd0f7980c84"

Rebuild your site with yarn clean; yarn develop and you should see your new content at https://localhost:8000 (the starter page is still the same!)

Cool URIs don't change is an entertaining article about why you should try to make your URIs permanent. An important step towards this goal is a naming convention that outlives any changes on your site.

A popular idea is to choose a date schema, where your article links are structured according to date. This is also common practice in archiving, and what couldn't be more permanent than an archive?

Instead of providing posts under the URI:


you could make them available under the URI:


effectively following a /{year}/{month}/{day}/{article-name} schema.

This schema can be enforced by making modifications to the permalink property in routes.yaml.

It's important to understand that routes.yaml is a file on your headless Ghost CMS and has no direct counterpart in your Gatsby configuration.

You can also download, modify and upload this file from the Ghost Admin interface:

Either change the file directly or upload a modified version in the admin panel:

# ./content/settings/routes.yaml


    permalink: /{year}/{month}/{day}/{slug}/
    template: index

  tag: /tag/{slug}/
  author: /author/{slug}/

Rebuild your site with yarn clean; yarn develop and look how your article links changed to permalinks (remember that the basePath option is still active):

This is magic: how does the static site builder know how you configured the router of your headless CMS? Easy: the full URL path of every site can be used to create every page with exactly the same path, effectively replicating all CMS routes!

Dynamic Variables

The permalinks in the shown configuration contain the dynamic variables {year}, {month} and {day}. Here is a list of all variables that you can use in your permalinks:

  • {id} - unique identifier, e.g. 67982e808bcf48110190efd67
  • {slug} - the post slug, e.g. my-article
  • {year} - publication year, e.g. 2020
  • {month} - publication month, e.g. 05
  • {day} - publication day, e.g. 28
  • {primary_tag} - slug of first tag listed in the post, e.g. news
  • {primary_author} - slug of first author, e.g. martin

If you think about which of these are permanent with respect to your post, the publication date, the slug and the primary author should outlive all changes to your website. If one of those change, it can be considered a different article.

The {id} could change, if you migrate your articles to a new blogging system, a news tag could be switched to an archived tag at later time. That's why the use of {id} and {primary_tag} is discouraged in permalinks.

Creating Collections

Collections are a great way to create distinct areas of non-overlapping content. A typical use case is different routes in a multi-language site that are segmented according to locales.

To configure collections, there is a new collections configuration available that you can add to the routesConfig.js file:

// routesConfig.js
module.exports = {

    basePath: `/blog`,
    collections: [{
        path: `speeches`,
        selector: node => node.primary_tag && node.primary_tag.slug === `speeches`,

As collections is an array, you can configure as many collections as you like. Each collection consists of a path and a selector. The path is added to the basePath and the selector is a Javascript function that operates on posts. JS functions are extremely flexible, so you can create collections based on tags, authors or on any other field that its available in posts!

The above configuration creates two collections, namely /speeches/ and everything else under /. Thus, you now have two index pages.

Note that Jamify collections ensure non-overlapping content, even if your selector functions overlap. This is done by applying selector functions only on the remainders of the previous selector results.

In order to be able to access the new /speeches/ collection, add a new navigation item under the Design and call it Speaker's Corner:

Save the new design and rebuild your site. When you inspect your site, you can see that all articles with primary tag speeches have different routes. They are all prefixed with /speeches/.

Also note that articles that fall into the speeches collection do not show up in the root index, so no duplicate content!

Finally, observe that taxonomies such as tags and authors remain unaffected. So, all articles for one author are shown even if they fall in different collections. Likewise, all articles for one tag are shown across collections.

If you are into details, have a look at the preview section at the bottom of each post. As you can see, previous and next posts are only shown for the ones that fall into the same collection. This is important if you use collections for multi-language sites, because you do not want references to posts from another locale.


Designing a site structure that is fun to navigate, easy to maintain and where URIs persist over many years despite all the changes that your site may go through, is definitely challenging.

That's why it is important that you acquire a solid understanding of the tools that Jamify provides for dynamic routing. You have seen how these tools can be applied to popular use cases such as integrating a landing page, using permalinks and collections. You are now ready to apply the same principles to your own site!

Do you want early access to Blogody, the brand new blogging platform that I am creating? Just sign-up on the new Blogody landing page and be among the first to get notified!