Yes, I know the title post is a bit long π but each listed feature is necessary to understand the solution that I adopted to achieve my goal: create a sitemap file for each of my domains pointing to a single multilingual, static, URL-translated Next.js site.
Why static?
First of all, I π the jamstack architecture that solves so many problems in such simple ways... and that is why in this context I speak of a static
site: all pages are pre-generated at build time (known as SSG).
Multi-language
Next.js give us a basic but powerful i18n support with two strategies: sub-path routing (example.com/it
, example.com/en
) and domain routing (example.it
, example.com
): I chose the last one, but you can adopt this solution also for the first strategy easily.
URLs translated
Translate the URLs too in my opinion is a killer SEO feature but, at the moment, is not so simple support it in a Next.js site: there are many solutions which vary by your data source (headless CMS, file system based, ...) but the important thing is that in your final build you'll have each page collected for its localization code:
.next/server/pages/it/chi-siamo.html
.next/server/pages/en/about-us.html
Thanks to one of Lee Robinson's post I have found a suggestion for an efficient way to generate all the necessary sitemap files without further queries or REST calls or server requests, but simply by looking in the created .next
directory (instead of in the pages
folder as in the original post).
import { writeFileSync } from "fs"
import { globby } from "globby"
import prettier from "prettier"
async function generate() {
;["it", "en"].forEach(async (lang) => {
const pages = await globby([
`.next/server/pages/${lang}/**/*.html`,
`!.next/server/pages/${lang}/404.html`,
`!.next/server/pages/${lang}/500.html`
])
const siteUrl = lang === "it" ? "https://example.it" : "https://example.com"
const sitemap = `
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>${siteUrl}</loc>
<lastmod>${new Date().toISOString()}</lastmod>
</url>
${pages
.map((page) => {
const route = page.replace(`.next/server/pages/${lang}`, "").replace(".html", "")
return `<url>
<loc>${siteUrl}${route}</loc>
<lastmod>${new Date().toISOString()}</lastmod>
</url>
`
})
.join("")}
</urlset>
`
const formatted = prettier.format(sitemap, {
parser: "html",
})
writeFileSync(`public/sitemap-${lang}.xml`, formatted)
})
}
generate()
Executing the generate-sitemap.mjs
script in the postbuild
step as described in the linked post, we'll have at the end two (or more as your site's language π) xml sitemap file:
example.com/sitemap-en.xml
example.it/sitemap-it.xml
Usually with a non-localized site I'm very happy to use the next-sitemap library but for a specific case like this I think that rolling up your sleeves is always the best solution π
Top comments (2)
Hi Marco, tried this and i wasn't getting it right
Here is what i got:
`node:internal/fs/utils:921
throw new ERR_INVALID_ARG_TYPE(
^
TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received an instance of Promise`
tried using for...of loop instead too, still the same
Wow! Awesome solution. Huge thanks for sharing this. ππ½