I love working with Ghost. It doesn't have nearly as many features as Wordpress, but it also doesn't have the baggage. That means that customizing it is incredibly simple and straightforward. Today I'll be adding a dynamic sitemap.xml which is generated from your posts. There is an outstanding github issue for this feature, but it looks like they won't be adding support for it until version 0.6. If you don't want to wait that long, and are comfortable modifying a few files there is no reason we can't get this working now. This walkthrough assumes you have a local copy of the project running and you will be deploying via git or github (something which will do npm install for you)

If you just want to see the diff, you can see it in this commit.

Be aware that if you do a global update of your ghost blog install it will wipe out your changes and you will have to re-implement this functionality. That's the cost of hacking in functionality outside of a plugin ecosystem.

The first step in getting this functionality is to add a node module for generating sitemaps so you don't have to generate the XML yourself. Through a quick search I found the appropriately named sitemap module which we will use for this example. Simply run npm install sitemap --save to get the module and update your package.json file for when you deploy later.

Now there are just two files to modify to add this functionality. The first file we'll modify will add the new route, which will map to the controller where we'll add the actual implementation.

/core/server/routes/frontend.js

    // ### Frontend routes
    server.get('/sitemap.xml', frontend.sitemap); // <- new line
    server.get('/rss/', frontend.rss);
    server.get('/rss/:page/', frontend.rss);

Now the implementation itself goes inside the controller.

/core/server/controllers/frontend.js

Add the reference to the sitemap module

when        = require('when'),
Route       = require('express').Route,
sm          = require('sitemap'), <- get reference to sitemap module

api         = require('../api'),

Add the function which actually creates the sitemap xml.

function buildSitemap(posts, done, sitemap) {
  var sitemap = sitemap || sm.createSitemap ({
    hostname: config().url,
    cacheTime: 600000
  });

  if(posts.length > 0) {
    var post = posts.shift();
    sitemap.add({ url: '/' + post.slug + '/' });
    process.nextTick(buildSitemap.bind(this, posts, done, sitemap));
  } else {
    sitemap.toXML(function(xml) {
      done(xml);
    });
  }
}

Lastly we need to add the function which the route is mapped to. This is added to the frontendControllers object.

frontendControllers = {
  'sitemap': function(req, res, next) {
    api.posts.browse({ staticPages: 'all', limit: 1000 }).then(function(result) {
      buildSitemap(result.posts, function(sitemap) {
        res.header('Content-Type', 'application/xml');
        res.send(sitemap);
      });
    });
  },
  'homepage': function (req, res, next) {

Now if you run git status you should see the files that we've changed.

To test locally, just run npm start and try requesting the sitemap.xml file. You should see something like the following:

Now you have a dynamic sitemap.xml! This code generates the sitemap every time a request is made to /sitemap.xml. In theory we could be caching this and only updating it when there is a change to one of the posts, but that's a micro optimization that isn't necessary for the vast majority of blogs running ghost. Hopefully someone finds this useful until ghost adds official support for sitemaps!

Here is a diff containing all the changes I made above.