Last active
April 7, 2025 09:24
-
-
Save destor/64c80e8c6e69e8ed840ef225cf90d8c3 to your computer and use it in GitHub Desktop.
Nextra 4 sitemap creation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { MetadataRoute } from "next"; | |
import { | |
Folder, | |
MdxFile, | |
Meta, | |
MetaJsonFile, | |
PageMapItem, | |
} from "nextra"; | |
import { getPageMap } from "nextra/page-map"; | |
import { URL } from "url"; | |
export const dynamic = "force-static"; | |
interface PageType { | |
title: string; | |
type?: "page"; | |
display?: "hidden" | "normal" | string; | |
} | |
interface SitemapEntry { | |
url: string; | |
lastModified: string; | |
} | |
export function isPageType(value: unknown): value is PageType { | |
if (typeof value !== "object" || value === null) { | |
return false; | |
} | |
const candidate = value as Record<string, unknown>; | |
if ("title" in candidate) { | |
if ("type" in candidate && candidate.type !== "page") { | |
return false; | |
} | |
return true; | |
} | |
return false; | |
} | |
const isMetaJSONFile = (value: unknown): value is MetaJsonFile => | |
typeof value === "object" && value !== null && "data" in value; | |
const isFolder = (value: unknown): value is Folder => | |
typeof value === "object" && | |
value !== null && | |
"name" in value && | |
"route" in value && | |
"children" in value; | |
const isMDXFile = (value: unknown): value is MdxFile => | |
typeof value === "object" && | |
value !== null && | |
"name" in value && | |
"route" in value && | |
"frontMatter" in value; | |
// Filter out hidden pages | |
const isNotHiddenPage = ([_, value]: [string, Meta]): boolean => | |
!isPageType(value) || value.display !== "hidden"; | |
const toSitemapEntry = (pageMapEntry: PageMapItem): SitemapEntry[] => { | |
if (isFolder(pageMapEntry)) { | |
return parsePageMapItems(pageMapEntry.children); | |
} else if (isMDXFile(pageMapEntry)) { | |
const { frontMatter, route } = pageMapEntry; | |
return [{ | |
url: route, | |
lastModified: frontMatter?.timestamp ? new Date(frontMatter.timestamp).toISOString() : new Date().toISOString(), | |
}]; | |
} | |
return []; | |
} | |
const parsePageMapItems = (items: PageMapItem[]): SitemapEntry[] => { | |
const metadata = Object.entries(items.find((item) => isMetaJSONFile(item))?.data ?? []); | |
const siteMapEntries: (SitemapEntry | null)[] = metadata | |
.filter(isNotHiddenPage) | |
.map(([key, _]) => items.find( | |
(item) => "name" in item && item.name === key | |
)) | |
.filter((item): item is PageMapItem => !!item) | |
.flatMap(pageMapEntry => toSitemapEntry(pageMapEntry)); | |
return siteMapEntries.filter((entry): entry is SitemapEntry => !!entry) | |
}; | |
const sitemap = async (): Promise<MetadataRoute.Sitemap> => { | |
const baseUrl = "https://example.com"; | |
const pageMap = await getPageMap(); | |
return parsePageMapItems(pageMap).map((entry) => ({ | |
url: new URL(entry.url, baseUrl).toString(), | |
lastModified: entry.lastModified, | |
})); | |
}; | |
export default sitemap; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment