Generate Dynamic Sitemap With Firebase Admin for Your Universal Nuxt App

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:
- Query the "blogs" collection for all published articles and sort the result by the creation date.
- Add the records to the sitemap.
- Return the sitemap with the correct headers.
Result
The result is a well-built sitemap that automatically gets updated whenever I publish new blogs.
