Pup

v2.x

ExtrasSitemap

Because Pup includes server side rendering out the box, to aid in the process of getting your app indexed by search engines, Pup includes a template for dynamically generating a sitemap.xml file.

Of note, the part that needs to be customized is the routes array near the top of the file. This describes the pages to be listed in the sitemap, each described by an object with a base property describing the base path for the route.

For routes that are dynamic—meaning, they're generated based on the creation of user content—a collection, query, and projection can be specified on the object for automatic mapping.

Once complete, your sitemap will be available at http://domain.com/sitemap.xml (e.g., http://localhost:3000/sitemap.xml).

/startup/server/sitemap.js
import xml from 'xml';
import { Meteor } from 'meteor/meteor';
import { Picker } from 'meteor/meteorhacks:picker';
import Documents from '../../api/Documents/Documents';
import { iso } from '../../modules/dates.js';

const baseUrl = Meteor.absoluteUrl();

// NOTE: Slash are omitted at front because it comes with baseUrl.
const routes = [
  { base: 'signup' },
  { base: 'login' },

  { base: 'verify-email' },
  { base: 'recover-password' },
  { base: 'reset-password' },

  { base: 'terms' },
  { base: 'privacy' },
  { base: 'example-page' },

  {
    base: 'documents',
    collection: Documents,
    // NOTE: Edit this query to limit what you publish.
    query: {},
    projection: { fields: { _id: 1, createdAt: 1 }, sort: { createdAt: -1 } },
  },
];

const sitemap = {
  urlset: [{ _attr: { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' } }],
};

routes.forEach(({ base, collection, query, projection }) => {
  const currentDateTime = new Date().toISOString();
  const urlTemplate = (path, date, priority) => ({
    url: [
      { loc: `${baseUrl}${path}` },
      { lastmod: iso(date) },
      { changefreq: 'monthly' },
      { priority },
    ],
  });

  sitemap.urlset.push(urlTemplate(base, currentDateTime, '1.0'));

  if (collection) {
    const items = collection.find(query, projection).fetch();
    if (items.length > 0) {
      items.forEach(({ _id, createdAt }) => {
        sitemap.urlset.push(urlTemplate(`${base}/${_id}`, createdAt, 0.5));
      });
    }
  }
});

Picker.route('/sitemap.xml', (params, request, response) => {
  response.writeHead(200, { 'Content-Type': 'application/xml' });
  response.end(xml(sitemap, { declaration: { standalone: 'yes', encoding: 'utf-8' } }));
});