Generate Dynamic Sitemap With Firebase Admin for Your Universal Nuxt App

Generate Dynamic Sitemap With Firebase Admin for Your Universal Nuxt App cover image

Photo by Clint Adair on Unsplash

It's almost always a requirement to have a sitemap for your website. Sitemaps help guide search engines crawl your website by telling search engines what pages you think are important.

Objective

I wanted requests to /sitemap.xml to return a dynamically generated sitemap that retrieves its data from Firebase.

Firebase Admin

I used Firebase Admin to retrieve the list of blogs. This is because I wanted to generate the sitemap on the server.

Install the Firebase Admin SDK:

npm install firebase-admin

Sitemap module

We will also need the help of the sitemap module to easily generate the sitemap.

Install the sitemap module:

npm install sitemap

Nuxt serverMiddleware

The serverMiddleware property lets us add custom middleware to handle requests. This allows us to add custom parsers, pre-process data, and even add custom routes.

In order to handle requests to /sitemap.xml, I added the custom middleware to the serverMiddleware property of the nuxt.config.js like so:

nuxt.config.js

serverMiddleware: [
  {
    path: '/sitemap.xml',
    handler: '~/serverMiddleware/sitemap.js'
  }
]

Request handler

For reusability purposes, we created a small Javascript to import and initialise Firebase admin serverMiddleware/firebase-admin.js:

serverMiddleware/firebase-admin.js

const admin = require('firebase-admin')
module.exports = admin.initializeApp({
  credential: admin.credential.applicationDefault()
})

I initialised the Firebase Admin SDK using the default application credentials. This will work if your app is hosted on Google App Engine.

Alternatively, you can generate a service account key and set the GOOGLE_APPLICATION_CREDENTIALS environment variable, or you can explicitly pass the path to the service account key in code.

Below is the main code that will generate our sitemap serverMiddleware/sitemap.js:

serverMiddleware/sitemap.js

const { createGzip } = require('zlib')
const { SitemapStream, streamToPromise } = require('sitemap')
const app = require('./firebase-admin')

export default function (req, res, next) {
  const db = app.firestore()
  const smStream = new SitemapStream({ hostname: 'https://example.com/' })
  const pipeline = smStream.pipe(createGzip())

  db.collection('blogs')
    .where('published', '==', true)
    .orderBy('created', 'desc')
    .get()
    .then((querySnapshot) => {
      for (const doc of querySnapshot.docs) {
        const data = doc.data()
        smStream.write({
          url: `/blog/${doc.id}`,
          lastmod: data.changed
            .toDate()
            .toISOString()
            .substr(0, 10)
        })
      }

      smStream.end()

      streamToPromise(pipeline).then((buffer) => {
        res.writeHead(200, {
          'Content-Type': 'application/xml',
          'Content-Encoding': 'gzip'
        })
        res.end(buffer)
      })
    })
}

This code does a number of things:

  1. Query the "blogs" collection for all published articles and sort the result by the creation date.
  2. Add the records to the sitemap.
  3. Return the sitemap with the correct headers.

Result

The result is a well-built sitemap that automatically gets updated whenever I publish new blogs.