import type { Metadata, Viewport } from "next"; import { Atkinson_Hyperlegible, EB_Garamond, Geist, Geist_Mono, Inter, JetBrains_Mono, Lora, Merriweather, Open_Sans, Playfair_Display, Source_Serif_4, Space_Grotesk, Work_Sans, } from "next/font/google"; import "./globals.css"; import type { Settings } from "@/db/schema"; import { FEED_PATH, resolveSiteUrl } from "@/lib/seo"; import { DEFAULT_SETTINGS, getSettings } from "@/lib/services/settings"; import { THEME_META } from "@/lib/themes"; // The entire site is driven by database content that admins change at any // time, so every route renders per request (no build-time DB dependency). // Incremental caching/revalidation is a documented future optimization. export const dynamic = "force-dynamic"; // All selectable body fonts are self-hosted by next/font at build time. // Only Geist (the default) is preloaded; the others declare @font-face // rules and are fetched by the browser solely when the admin-selected // data-font attribute makes one of them the active --font-body. const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], }); const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], }); const inter = Inter({ variable: "--font-inter", subsets: ["latin"], preload: false, }); const lora = Lora({ variable: "--font-lora", subsets: ["latin"], style: ["normal", "italic"], preload: false, }); const merriweather = Merriweather({ variable: "--font-merriweather", subsets: ["latin"], weight: ["300", "400", "700"], style: ["normal", "italic"], preload: false, }); const jetbrainsMono = JetBrains_Mono({ variable: "--font-jetbrains-mono", subsets: ["latin"], style: ["normal", "italic"], preload: false, }); const sourceSerif = Source_Serif_4({ variable: "--font-source-serif", subsets: ["latin"], style: ["normal", "italic"], preload: false, }); const ebGaramond = EB_Garamond({ variable: "--font-eb-garamond", subsets: ["latin"], style: ["normal", "italic"], preload: false, }); const playfair = Playfair_Display({ variable: "--font-playfair", subsets: ["latin"], style: ["normal", "italic"], preload: false, }); const openSans = Open_Sans({ variable: "--font-open-sans", subsets: ["latin"], style: ["normal", "italic"], preload: false, }); const workSans = Work_Sans({ variable: "--font-work-sans", subsets: ["latin"], style: ["normal", "italic"], preload: false, }); const atkinson = Atkinson_Hyperlegible({ variable: "--font-atkinson", subsets: ["latin"], weight: ["400", "700"], style: ["normal", "italic"], preload: false, }); const spaceGrotesk = Space_Grotesk({ variable: "--font-space-grotesk", subsets: ["latin"], preload: false, }); const fontVariables = [ geistSans.variable, geistMono.variable, inter.variable, lora.variable, merriweather.variable, jetbrainsMono.variable, sourceSerif.variable, ebGaramond.variable, playfair.variable, openSans.variable, workSans.variable, atkinson.variable, spaceGrotesk.variable, ].join(" "); /** The chrome must render even when the DB is down; fall back to defaults. */ async function settingsOrDefaults(): Promise { try { return await getSettings(); } catch { return DEFAULT_SETTINGS; } } export async function generateMetadata(): Promise { const settings = await settingsOrDefaults(); return { // Resolves every relative canonical/OG URL below to the public origin. metadataBase: new URL(resolveSiteUrl(settings)), title: { default: settings.siteTitle, template: `%s ยท ${settings.siteTitle}` }, description: settings.headerText || undefined, alternates: { types: { "application/rss+xml": [{ url: FEED_PATH, title: settings.siteTitle }] }, }, openGraph: { siteName: settings.siteTitle, type: "website", locale: "en", }, }; } export async function generateViewport(): Promise { const { theme } = await settingsOrDefaults(); return { themeColor: THEME_META[theme].bg }; } export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { const { theme, font } = await settingsOrDefaults(); return ( Skip to content {children} ); }