Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
NextJS sitemap route

NextJS sitemap route

The current setup has been tested with NextJS 9.2.2

Installation

Create a file called sitemap.js into pages/api.
Api Routes are special pages that allow you to export a request handler function that will be run as a serverless function. Using Api Routes avoid you the pain of overriding the default server script, although if have already done it what follows is pretty much the same.

Install the following package to build the sitemap xml with confidence.

npm install xmlbuilder

If you have dynamic routes you should get a server side fetching library, I'm going to use isomorphic-unfetch but feel free to choose your favorite one.
Please note that the syntax may be slightly different on other libraries.

npm install isomorphic-unfetch

Create environment variables using a dotenv file or some other techinque.
For demonstration i will suppose that your cms exposes a graphql endpoint.

SITE_URL=https://example.com
API_URL=https://admin.example.com

Usage

When the code is up and running you can see the result by visiting https://example.com/api/sitemap.

// ============
// routes.js
// ============
// TODO: blacklist could be improved by using regex selectors instead of strings
const blacklist = ["/private/route"];
// Checks if route is black listed
const isValidRoute = slug => blacklist.some(item => item === slug) === false;
// TODO: Adapt this to your api
const query = `
{
pages {
edges {
node {
slug
childPages {
edges {
node {
slug
}
}
}
}
}
}
posts {
edges {
node {
slug
}
}
}
}
`;
export async function getRoutes() {
// Request data
const result = await fetch(process.env.API_URL + "/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ variables: {}, query })
});
// Parse response as json
const { data } = await result.json();
let pages = [];
// Parse data to retrieve page slugs
// Note: this depends on how your're data is structured.
if (data.pages.edges) {
// Loop through every parent page
data.pages.edges.forEach(({ node: parentNode }) => {
const parentSlug = "/" + parentNode.slug;
// Check if slug is blacklisted
if (isValidRoute(parentSlug)) {
// Grab parent page URL
pages.push(parentSlug);
}
// If there are any child page
if (parentNode.childPages.edges) {
// Loop through every child page
parentNode.childPages.edges.forEach(({ node: childNode }) => {
const slug = parentSlug + "/" + childNode.slug;
// Check if slug is blacklisted
if (isValidRoute(slug)) {
// Grab child page url
pages.push(slug);
}
});
}
});
}
if (data.posts.edges) {
// Loop through every interior design post
data.posts.edges.forEach(({ node }) => {
const slug = "/posts/" + node.slug;
// Check if slug is blacklisted
if (isValidRoute(slug)) {
// Grab post url
pages.push(slug);
}
});
}
return pages;
}
// pages/api/sitemap.js
import builder from "xmlbuilder";
import fetch from "isomorphic-unfetch";
import { getRoutes } from "../../routes.js";
export default async (req, res) => {
// Create root element
let root = builder.create("urlset", {
version: "1.0",
encoding: "UTF-8",
});
// Set root element attribute
root.att({ xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9" });
// Get routes from API
const routes = await getRoutes();
// Insert a url element for each route
routes.forEach((slug) => {
const url = root.ele("url");
url.ele("loc", process.env.siteUrl + slug);
url.ele("priority", 0.5);
url.ele("changefreq", "always");
});
let xml = root.end({ pretty: true });
res.statusCode = 200;
res.setHeader("Content-Type", "text/xml");
res.end(xml);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.