Nested hierarchical long URLs, subcategories tree routes.yaml


I need to migrate a Ghost 3.16.1 to old school long nested URLs with category posts and their related posts in each category. This is an attempt to document a solution for those who have also read everything touching on the subject.

The blog content is already vast and the categories (4 levels) won’t move much so I’ll go with generating routes.yaml Collections and the redirects JSON. I don’t see another solution if we want:

  • to define our static URLs (Channels and Routes do not change the post URL)
  • to avoid duplicate content
  • to have sitemap.xml automatically update to the new URL structure

Every post has an ordered list of tags and their slugs are used to build the URLs.

  • post.slug: cat4post
  • tag.slugs: category1, category2, category3, category4
  • expected URL: /category1/category2/category3/category4/cat4post/

A category post is tagged in addition with an internal #level1, #level2, #level3 or #level4 so they can be filtered in or out. For example in post.hbs related posts, to navigate down the tree i.e. show links to sibling posts on the same level and also to the subcategory post entry points.

  • post.slug: category4
  • tag.slugs: category1, category2, category3, category4, hash-level4
  • expected URL: /category1/category2/category3/category4/

The routes are matched in the order they are declared so we start with the longest. Collection names can be anything as long as they’re unique. We’ll stick to URL naming for clarity.


### LEVEL4 cluster articles in one go
### excluding the category post to avoid doubling the URL like .../category4/category4/
  permalink: /cours-code/guides/route/autoroute/{slug}/
  filter: primary_tag:category1+tag:category2+tag:category3+tag:category4+tag:-hash-level4
  template: post
  data: post.slug

### LEVEL4 the category post
  permalink:  /category1/category2/category3/category4/
  filter: primary_tag:category1+tag:category2+tag:category3+tag:category4+tag:hash-level4
  template: post
  data: post.slug

Checking the DEBUG ghost:services:routing:bootstrap Routes and ghost:services:url:urls they show what we expect.

Issue #1: The category post URL /category1/category2/category3/category4/ throws a 404 Page not found.

I can see a workaround by removing the last category post tag category4 which will push it up a level however I am curious as to why this doesn’t work.

Something I haven’t understood? A limitation? A bug in this version of Ghost?

PS: I hope my markdown is readable and yes, for the good of all I will update with my full solution after discussion here.