mirror of
https://github.com/UiO-Gaming/uiogaming.no.git
synced 2026-11-05 13:56:47 +01:00
Render pages statically using next/root-params
getLocale() read request headers, which made every route dynamic and the page-level revalidate a no-op. Make [locale]/layout.tsx the root layout so the locale can be read through next/root-params, set <html lang>, add generateStaticParams for locales and slugs, and pass the locale explicitly to metadata translations. The middleware now matches all non-file paths so URLs without a locale are redirected instead of hitting the root layout's notFound(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a8b31a5379
commit
da3d26d16b
9 files changed
+77
-79
No files matched your search
@@ -8,7 +8,6 @@ import { getBlogPost } from "@/lib/sanity"
|
||||
import { PortableText, PortableTextComponents } from "@portabletext/react"
|
||||
import { Metadata, Viewport } from "next"
|
||||
import { notFound } from "next/navigation"
|
||||
import { getLocale } from "next-intl/server"
|
||||
import Image from "next/image"
|
||||
|
||||
import * as styles from "./page.module.css"
|
||||
@@ -33,15 +32,21 @@ interface LinkValue {
|
||||
href: string
|
||||
}
|
||||
|
||||
// Render posts on first visit and cache them, instead of on every request
|
||||
export async function generateStaticParams() {
|
||||
return []
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: BlogPageParams): Promise<Metadata> {
|
||||
const { slug } = await params
|
||||
const { slug, locale } = await params
|
||||
const data = await getBlogPost(slug)
|
||||
|
||||
if (!data) notFound()
|
||||
|
||||
return generateSeoMetadata({
|
||||
locale,
|
||||
title: data.title,
|
||||
description: data.excerpt,
|
||||
imageURL: data.mainImage?.asset?.url,
|
||||
@@ -94,8 +99,7 @@ const components: PortableTextComponents = {
|
||||
}
|
||||
|
||||
const BlogPost = async ({ params }: BlogPageParams) => {
|
||||
const { slug }: { slug: string } = await params
|
||||
const locale = await getLocale()
|
||||
const { slug, locale } = await params
|
||||
|
||||
const data = await getBlogPost(slug)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getEvent } from "@/lib/sanity"
|
||||
|
||||
import { Metadata, Viewport } from "next"
|
||||
import { notFound } from "next/navigation"
|
||||
import { getLocale, getTranslations } from "next-intl/server"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import { FaCalendarAlt, FaMapMarkerAlt } from "react-icons/fa"
|
||||
|
||||
import * as styles from "./page.module.css"
|
||||
@@ -17,16 +17,20 @@ interface EventPageParams {
|
||||
}>
|
||||
}
|
||||
|
||||
// Render events on first visit and cache them, instead of on every request
|
||||
export async function generateStaticParams() {
|
||||
return []
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: EventPageParams): Promise<Metadata> {
|
||||
const { slug } = await params
|
||||
const { slug, locale } = await params
|
||||
const data = await getEvent(slug)
|
||||
|
||||
if (!data) notFound()
|
||||
|
||||
const locale = await getLocale()
|
||||
const t = await getTranslations("metaTags")
|
||||
const t = await getTranslations({ locale, namespace: "metaTags" })
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString)
|
||||
@@ -43,6 +47,7 @@ export async function generateMetadata({
|
||||
}
|
||||
|
||||
return generateSeoMetadata({
|
||||
locale,
|
||||
title: data.title,
|
||||
description: `${t("time")}: ${formatDate(data.date)}\n${t("location")}: ${data.location}\n\n${data.description}`,
|
||||
})
|
||||
@@ -53,8 +58,7 @@ export function generateViewport(): Viewport {
|
||||
}
|
||||
|
||||
const Event = async ({ params }: EventPageParams) => {
|
||||
const { slug }: { slug: string } = await params
|
||||
const locale = await getLocale()
|
||||
const { slug, locale } = await params
|
||||
|
||||
const data = await getEvent(slug)
|
||||
|
||||
|
||||
+34
-13
@@ -1,5 +1,7 @@
|
||||
// app/[locale]/layout.tsx
|
||||
import "@/app/globals.css"
|
||||
|
||||
import { hasLocale, NextIntlClientProvider } from "next-intl"
|
||||
import type { Metadata } from "next"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import LanguageSelector from "@components/ui/languageSelector"
|
||||
@@ -7,7 +9,22 @@ import UmamiAnalytics from "@/components/umamiAnalytics"
|
||||
import { locales } from "@/i18n"
|
||||
import { getAnalyticsConfig } from "@/lib/analyticsConfig"
|
||||
|
||||
export default async function LocaleLayout({
|
||||
export const metadata: Metadata = {
|
||||
title: "UiO Gaming",
|
||||
description:
|
||||
"En hobbyforening som ønsker skape et sosialt miljø rundt gaming og e-sport på UiO!",
|
||||
icons: {
|
||||
icon: "/logo_2024_neon_pink.png",
|
||||
shortcut: "/logo_2024_neon_pink.png",
|
||||
apple: "/logo_2024_neon_pink.png",
|
||||
},
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map(locale => ({ locale }))
|
||||
}
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
@@ -24,16 +41,20 @@ export default async function LocaleLayout({
|
||||
const analyticsConfig = getAnalyticsConfig()
|
||||
|
||||
return (
|
||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||
<LanguageSelector />
|
||||
<main>{children}</main>
|
||||
{analyticsConfig?.enabled && (
|
||||
<UmamiAnalytics
|
||||
websiteId={analyticsConfig.websiteId}
|
||||
src={analyticsConfig.src}
|
||||
domains={analyticsConfig.domains}
|
||||
/>
|
||||
)}
|
||||
</NextIntlClientProvider>
|
||||
<html lang={locale}>
|
||||
<body>
|
||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||
<LanguageSelector />
|
||||
<main>{children}</main>
|
||||
{analyticsConfig?.enabled && (
|
||||
<UmamiAnalytics
|
||||
websiteId={analyticsConfig.websiteId}
|
||||
src={analyticsConfig.src}
|
||||
domains={analyticsConfig.domains}
|
||||
/>
|
||||
)}
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -11,8 +11,18 @@ import { Metadata, Viewport } from "next"
|
||||
|
||||
export const revalidate = 3600 // 1 hour
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return generateSeoMetadata({})
|
||||
interface HomePageParams {
|
||||
params: Promise<{
|
||||
locale: string
|
||||
}>
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: HomePageParams): Promise<Metadata> {
|
||||
const { locale } = await params
|
||||
|
||||
return generateSeoMetadata({ locale })
|
||||
}
|
||||
|
||||
export function generateViewport(): Viewport {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import "@/app/globals.css"
|
||||
|
||||
import type { Metadata } from "next"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "UiO Gaming",
|
||||
description:
|
||||
"En hobbyforening som ønsker skape et sosialt miljø rundt gaming og e-sport på UiO!",
|
||||
icons: {
|
||||
icon: "/logo_2024_neon_pink.png",
|
||||
shortcut: "/logo_2024_neon_pink.png",
|
||||
apple: "/logo_2024_neon_pink.png",
|
||||
},
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import NotFound from "@/components/page-sections/notFound"
|
||||
|
||||
import { defaultLocale } from "@/i18n"
|
||||
|
||||
import { Metadata } from "next"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const t = await getTranslations({ locale: defaultLocale, namespace: "404" })
|
||||
|
||||
return { title: `${t("title")} | UiO Gaming` }
|
||||
}
|
||||
|
||||
// Rendered when [locale]/layout.tsx rejects an unknown locale (e.g. /de),
|
||||
// so there is no valid locale to use
|
||||
export default function RootNotFound() {
|
||||
return <NotFound locale={defaultLocale} />
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Metadata, Viewport } from "next"
|
||||
import { getLocale, getTranslations } from "next-intl/server"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
|
||||
type SeoProps = {
|
||||
locale: string
|
||||
title?: string
|
||||
description?: string
|
||||
imageURL?: string
|
||||
@@ -9,13 +10,13 @@ type SeoProps = {
|
||||
}
|
||||
|
||||
export async function generateSeoMetadata({
|
||||
locale,
|
||||
title,
|
||||
description,
|
||||
imageURL,
|
||||
author,
|
||||
}: SeoProps): Promise<Metadata> {
|
||||
const t = await getTranslations("metaTags")
|
||||
const locale = await getLocale()
|
||||
const t = await getTranslations({ locale, namespace: "metaTags" })
|
||||
|
||||
const defaultTitle = "UiO Gaming"
|
||||
const metaDescription = description || t("description")
|
||||
|
||||
+6
-5
@@ -1,14 +1,15 @@
|
||||
import { hasLocale } from "next-intl"
|
||||
import { getRequestConfig } from "next-intl/server"
|
||||
import { locale as rootLocale } from "next/root-params"
|
||||
|
||||
export const locales = ["no", "en"] as const
|
||||
export const defaultLocale = "no"
|
||||
const defaultLocale = "no"
|
||||
|
||||
export default getRequestConfig(async ({ locale, requestLocale }) => {
|
||||
export default getRequestConfig(async ({ locale }) => {
|
||||
// An explicit locale (e.g. getTranslations({ locale })) takes precedence.
|
||||
// Fall back to the default instead of throwing, so pages rendered outside
|
||||
// a valid locale (like the root not-found page) can still be translated
|
||||
const requested = locale ?? (await requestLocale)
|
||||
// The root param can be any first path segment until the layout rejects it,
|
||||
// so fall back to the default instead of throwing
|
||||
const requested = locale ?? (await rootLocale())
|
||||
const resolved = hasLocale(locales, requested) ? requested : defaultLocale
|
||||
|
||||
return {
|
||||
|
||||
+3
-2
@@ -7,6 +7,7 @@ export default createMiddleware({
|
||||
})
|
||||
|
||||
export const config = {
|
||||
// Match only internationalized pathnames
|
||||
matcher: ["/", "/(no|en)/:path*"],
|
||||
// Match everything except API routes, Next internals and files with an
|
||||
// extension, so paths without a locale get redirected to one
|
||||
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
|
||||
}
|
||||
Reference in new issue
Block a user