diff --git a/src/app/[locale]/blog/[slug]/page.tsx b/src/app/[locale]/blog/[slug]/page.tsx index 7df83f1..80024bd 100644 --- a/src/app/[locale]/blog/[slug]/page.tsx +++ b/src/app/[locale]/blog/[slug]/page.tsx @@ -64,10 +64,12 @@ export async function generateMetadata({ return generateSeoMetadata({ locale, + path: `/blog/${slug}`, title: data.title, description: data.excerpt, imageURL: data.mainImage?.asset?.url, author: data.author?.name, + publishedTime: data._createdAt, }) } diff --git a/src/app/[locale]/event/[slug]/page.tsx b/src/app/[locale]/event/[slug]/page.tsx index a2ad221..e23e035 100644 --- a/src/app/[locale]/event/[slug]/page.tsx +++ b/src/app/[locale]/event/[slug]/page.tsx @@ -36,6 +36,7 @@ export async function generateMetadata({ return generateSeoMetadata({ locale, + path: `/event/${slug}`, title: data.title, description: [ `${t("time")}: ${formatEventDate(data.date, locale)}`, diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 7ea79f5..8dc77f8 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -6,7 +6,7 @@ import { getMessages } from "next-intl/server" import { notFound } from "next/navigation" import LanguageSelector from "@/components/ui/languageSelector" -import { THEME_COLOR } from "@/components/seo" +import { SITE_URL, THEME_COLOR } from "@/components/seo" import UmamiAnalytics from "@/components/umamiAnalytics" import { routing } from "@/i18n/routing" @@ -16,6 +16,8 @@ import { routing } from "@/i18n/routing" const CLIENT_NAMESPACES = ["about", "buttons", "common", "office"] export const metadata: Metadata = { + // Resolves relative URLs in metadata, like Open Graph images and alternates + metadataBase: new URL(SITE_URL), title: "UiO Gaming", description: "En hobbyforening som ønsker skape et sosialt miljø rundt gaming og e-sport på UiO!", diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx index 19b8930..d7ab8b9 100644 --- a/src/app/[locale]/page.tsx +++ b/src/app/[locale]/page.tsx @@ -23,7 +23,7 @@ export async function generateMetadata({ }: HomePageParams): Promise { const { locale } = await params - return generateSeoMetadata({ locale }) + return generateSeoMetadata({ locale, path: "/" }) } export default function Home() { diff --git a/src/components/seo.tsx b/src/components/seo.tsx index 63476ab..65f94fd 100644 --- a/src/components/seo.tsx +++ b/src/components/seo.tsx @@ -2,38 +2,65 @@ import { Metadata } from "next" import type { Locale } from "next-intl" import { getTranslations } from "next-intl/server" +import { routing } from "@/i18n/routing" + +export const SITE_URL = "https://uiogaming.no" export const THEME_COLOR = "#7d36e7" type SeoProps = { locale: Locale + /** Path without the locale prefix, e.g. "/" or "/event/some-slug" */ + path: string title?: string description?: string imageURL?: string author?: string + /** Marks the page as an article, e.g. a blog post */ + publishedTime?: string } export async function generateSeoMetadata({ locale, + path, title, description, imageURL, author, + publishedTime, }: SeoProps): Promise { const t = await getTranslations({ locale, namespace: "metaTags" }) const defaultTitle = "UiO Gaming" const metaDescription = description || t("description") - const siteUrl = "https://uiogaming.no" + const localizedPath = (pageLocale: Locale) => + path === "/" ? `/${pageLocale}` : `/${pageLocale}${path}` return { title: title ? `${title} | ${defaultTitle}` : defaultTitle, description: metaDescription, authors: author ? [{ name: author }] : [{ name: "UiO Gaming" }], + // 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), + languages: { + ...Object.fromEntries( + routing.locales.map(pageLocale => [ + pageLocale, + localizedPath(pageLocale), + ]) + ), + "x-default": path, + }, + }, openGraph: { title: title || defaultTitle, description: metaDescription, - type: "website", + ...(publishedTime + ? { type: "article", publishedTime, authors: author && [author] } + : { type: "website" }), locale: locale, + url: localizedPath(locale), siteName: defaultTitle, images: imageURL ? [ @@ -46,7 +73,7 @@ export async function generateSeoMetadata({ ] : [ { - url: `${siteUrl}/logo_2024_neon_trans.png`, + url: "/logo_2024_neon_trans.png", alt: defaultTitle, }, ], @@ -55,7 +82,7 @@ export async function generateSeoMetadata({ card: imageURL ? "summary_large_image" : "summary", title: title || defaultTitle, description: metaDescription, - images: imageURL ? [imageURL] : [`${siteUrl}/logo_2024_neon_trans.png`], + images: imageURL ? [imageURL] : ["/logo_2024_neon_trans.png"], }, other: { "msapplication-TileColor": THEME_COLOR,