Blogging with Ghost is great, but its support for categories is limited. We're stuck with a monolithic format for slugs, and instead of having pretty links like
food/NYC/Ippudo, we get URLs like
my-fun-beach-trip. Not a massive deal, but it kills the polish.
This is more of a design choice than a serious technical restriction. In fact, you can change the internals of Ghost to support slugs with slashes, and even if you cannot autogenerate pretty URLs using categories, you should be able to manually specify the full path to each of your posts. Here's how:
Upgrade to the latest version of Ghost. These instructions are confirmed to work with
0.11.10and some previous versions as well.
Navigate to the root folder of your Ghost installation. This should contain folders like
Download this Gist, and replace
core/server/controllers/frontend/post-lookup.jswith the downloaded file. The code to look up posts has been amended for slugs with slashes.
core/built/assets/ghost.min.jsin your favorite text editor (Vim). Search for
encodeUriComponentin the file, and you should see a snippet of code starting with
api("slugs"and ending with
encodeUriComponent(t)). Replace that entire snippet (being sure to include the trailing parenthesis) with
api("slugs",e).slice(0,-5)+encodeURIComponent(t).replace(/%2F/g, '/'). Now the post editor will correctly send slugs with slashes to the server.
core/server/utils/index.js. Look for a variable called
safeString. You should see a portion of that string that looks like
|\/. Delete those three characters only! This will stop the server from sanitizing slashes in slugs.
core/server/routes/api.js. Find the string that looks like
slugs/:type/:name, and change it to
slugs/:name(*). The API routing handler will correctly capture slugs with slashes in the URL.
core/server/api/slugs.js. Look for a
returnstatement that is returning an
unknownSlugTypeerror. It should be contained inside an
ifstatement. Comment out the
Above the commented-out
returnstatement, add a line that looks like
options.type = 'post';, and then another line that looks like
if (options.data.name.slice(-1) === '/') options.data.name = options.data.name.slice(0, -1);. This will prevent the server from complaining that there is no post type associated with the slug, and remove a trailing slash from the final output.
Whew! You're done. Now go be pedantic about some other piece of technology. (Thanks to Matthew Miller for inspiring some of these changes.)