Add sitemap.xml and robots.txt

List the home page, blog posts and events in both languages with
hreflang alternates, and point crawlers to the sitemap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
leanderandClaude Opus 5.5 committed 2026-10-30 23:01:49 +01:00
1 parent 2e54e32ea6
commit b60f8a92e1
4 files changed
+90 -5

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
import { SITE_URL } from "@/components/seo"
import type { MetadataRoute } from "next"
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
disallow: "/api/",
},
sitemap: `${SITE_URL}/sitemap.xml`,
}
}
+39
View File
@@ -0,0 +1,39 @@
import { getLocalizedPath, SITE_URL } from "@/components/seo"
import { routing } from "@/i18n/routing"
import { getSitemapDocuments } from "@/lib/sanity"
import type { MetadataRoute } from "next"
export const revalidate = 3600 // 1 hour
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const { posts, events } = await getSitemapDocuments()
const pages: { path: string; lastModified?: string }[] = [
{ path: "/" },
...posts.map(post => ({
path: `/blog/${post.slug}`,
lastModified: post._updatedAt,
})),
...events.map(event => ({
path: `/event/${event.slug}`,
lastModified: event._updatedAt,
})),
]
// One entry per language version, each linking to the other versions
return pages.flatMap(({ path, lastModified }) => {
const languages = Object.fromEntries(
routing.locales.map(locale => [
locale,
`${SITE_URL}${getLocalizedPath(locale, path)}`,
])
)
return routing.locales.map(locale => ({
url: languages[locale],
lastModified,
alternates: { languages },
}))
})
}
+7 -5
View File
@@ -7,6 +7,10 @@ import { routing } from "@/i18n/routing"
export const SITE_URL = "https://uiogaming.no"
export const THEME_COLOR = "#7d36e7"
/** Adds the locale prefix to a path, e.g. ("en", "/event/x") -> "/en/event/x" */
export const getLocalizedPath = (locale: Locale, path: string) =>
path === "/" ? `/${locale}` : `/${locale}${path}`
type SeoProps = {
locale: Locale
/** Path without the locale prefix, e.g. "/" or "/event/some-slug" */
@@ -32,8 +36,6 @@ export async function generateSeoMetadata({
const defaultTitle = "UiO Gaming"
const metaDescription = description || t("description")
const localizedPath = (pageLocale: Locale) =>
path === "/" ? `/${pageLocale}` : `/${pageLocale}${path}`
return {
title: title ? `${title} | ${defaultTitle}` : defaultTitle,
@@ -42,12 +44,12 @@ export async function generateSeoMetadata({
// Tells search engines that the Norwegian and English versions are the
// same page. The unprefixed path redirects based on the browser language
alternates: {
canonical: localizedPath(locale),
canonical: getLocalizedPath(locale, path),
languages: {
...Object.fromEntries(
routing.locales.map(pageLocale => [
pageLocale,
localizedPath(pageLocale),
getLocalizedPath(pageLocale, path),
])
),
"x-default": path,
@@ -60,7 +62,7 @@ export async function generateSeoMetadata({
? { type: "article", publishedTime, authors: author && [author] }
: { type: "website" }),
locale: locale,
url: localizedPath(locale),
url: getLocalizedPath(locale, path),
siteName: defaultTitle,
images: imageURL
? [
+30
View File
@@ -144,3 +144,33 @@ export async function getBoardMembers() {
}
)
}
export async function getSitemapDocuments(): Promise<{
posts: { slug: string; _updatedAt: string }[]
events: { slug: string; _updatedAt: string }[]
}> {
const query = `
{
"posts": *[_type == "post" && defined(slug.current)] {
"slug": slug.current,
_updatedAt
},
"events": *[_type == "event" && defined(slug.current)] {
"slug": slug.current,
_updatedAt
}
}
`
return sanityClient.fetch(
query,
{},
{
cache: "force-cache",
next: {
tags: ["blog-posts", "events"],
revalidate: 3600, // 1 hour
},
}
)
}