Skip to content

Instantly share code, notes, and snippets.

@arvigeus
Created September 7, 2020 08:28
Show Gist options
  • Save arvigeus/100c732019dff5619b32cc41cad2791e to your computer and use it in GitHub Desktop.
Save arvigeus/100c732019dff5619b32cc41cad2791e to your computer and use it in GitHub Desktop.
Next.js sitemap
const { promises: fs } = require("fs");
const path = require("path");
interface FileInfo {
page: string;
lastModified: Date;
}
const pagesDir = `${process.cwd()}/src/pages/`;
async function getAllFiles(): Promise<FileInfo[]> {
const files: FileInfo[] = [];
for await (const file of getFiles(pagesDir)) files.push(file);
return files;
}
async function* getFiles(dir: string): AsyncGenerator<FileInfo> {
// Get all files of the current directory & iterate over them
const files = await fs.readdir(dir);
for (const file of files) {
// Construct whole file-path & retrieve file's stats
const filePath = `${dir}${file}`;
const fileStat = await fs.stat(filePath);
if (!isValid(file, dir, fileStat)) continue;
if (fileStat.isDirectory()) {
// Recurse one folder deeper
for await (const innerFile of getFiles(`${filePath}/`)) yield innerFile;
} else {
// Check if page is dynamic route
if (/\[.*\]\.(jsx?|tsx)/.test(file)) {
const page = require(filePath);
// Dynamic paths are generated at build time
if (page.getStaticPaths) {
const {
paths,
}: {
paths: { params: { [key: string]: string } }[];
} = page.getStaticPaths();
for (const { params } of paths) {
let dynamicPath = file;
for (const [param, value] of Object.entries(params))
dynamicPath = dynamicPath.replace(`[${param}]`, value);
yield {
page: `${cleanFileName(`${dir}${dynamicPath}`)}`,
lastModified: fileStat.mtime,
};
}
} else {
const page = `/${cleanFileName(filePath)}`;
console.info(
`\`/${page}\` is dynamic page and must be obtained at runtime`
);
}
}
// Page is normal route
else {
yield {
page: `/${cleanFileName(filePath)}`,
lastModified: fileStat.mtime,
};
}
}
}
}
const isValid = (file: string, parentDir: string, fileStat): boolean => {
const isRoot = parentDir === pagesDir;
const isDir = fileStat.isDirectory();
if (isRoot && isDir && file === "api") return false;
if (isRoot && !isDir && /^404\.(jsx?|tsx)$/.test(file)) return false;
if (file.startsWith("_")) return false;
if (file)
if (
!file.endsWith(".js") &&
!file.endsWith(".jsx") &&
!file.endsWith(".tsx") &&
!file.endsWith(".md") &&
!file.endsWith(".mdx")
)
return false;
return true;
};
const cleanFileName = (fileName: string): string => {
const fixed = fileName
.substr(0, fileName.lastIndexOf("."))
.replace(pagesDir, "");
return fixed.endsWith("/index") || fixed === "index"
? fixed.substr(0, fixed.lastIndexOf("/") + 1)
: fixed;
};
const formatDate = (date: Date): string => {
let month = String(date.getMonth() + 1),
day = String(date.getDate()),
year = date.getFullYear();
if (month.length < 2) month = `0${month}`;
if (day.length < 2) day = `0${day}`;
return [year, month, day].join("-");
};
export function createSiteMap(items: FileInfo[]): string {
const lines = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> ',
];
for (const { page, lastModified } of items) {
lines.push("<url>");
lines.push(`<loc>${page}</loc>`);
lines.push(`<lastmod>${formatDate(lastModified)}</lastmod>`);
lines.push("</url>");
}
lines.push("</urlset>");
return lines.join("");
}
export const writeSiteMap = (sitemap: string) => {
const parentDir = path.basename(path.dirname(pagesDir));
const publicDir = `${parentDir}public/`;
if (!fs.existsSync(publicDir)) fs.mkdirSync(publicDir);
fs.writeFileSync(`${publicDir}sitemap.xml`, sitemap);
};
export default getAllFiles;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment