Files
bootnode/web/app/layout.tsx
zooqueenandHanzo Dev 95d9ac6fa9 web: decomplect brand into runtime white-label config
Remove the hardcoded 5-brand catalog from web/lib/brand.ts; resolve the
active brand at runtime by hostname from an external catalog
(window.__BRAND_CATALOG__ / /config.json), mirroring the @hanzo/id SPA.
Ship one neutral built-in bootnode fallback. Add BrandProvider that loads
the per-deploy catalog client-side after hydration.

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-17 00:10:05 -07:00

86 lines
3.0 KiB
TypeScript

import type { Metadata } from "next"
import { GeistSans } from "geist/font/sans"
import { GeistMono } from "geist/font/mono"
import { ThemeProvider } from "@/components/theme-provider"
import { AuthProvider } from "@/lib/auth"
import { Providers } from "@/app/providers"
import { Toaster } from "@/components/ui/sonner"
import { BrandProvider } from "@/components/brand-provider"
import { getBrand } from "@/lib/brand"
import "@/app/globals.css"
// Static export: the prerendered HTML is brand-neutral. There is no
// request-time host detection here, and source carries no brand catalog — so
// SSG metadata uses bootnode's built-in neutral default. The real brand is
// resolved CLIENT-SIDE after hydration by BrandProvider, which loads the
// per-deploy catalog (window.__BRAND_CATALOG__ / /config.json) and re-resolves
// title/favicon/logos by hostname. Mirrors the @hanzo/id SPA.
function getServerBrand() {
const b = getBrand()
return { name: b.name, tagline: b.tagline, domain: b.domain, icon: b.favicon, defaultTheme: b.defaultTheme }
}
export function generateMetadata(): Metadata {
const b = getServerBrand()
return {
title: {
default: `${b.name} - ${b.tagline}`,
template: `%s | ${b.name}`,
},
description: `Enterprise blockchain infrastructure powered by ${b.name}. Multi-chain RPC, Token APIs, NFT APIs, Smart Wallets, Webhooks, and more.`,
keywords: ["blockchain", "RPC", "API", "Web3", "Ethereum", "Solana", "NFT", "DeFi", "smart contracts", "developer tools", b.name.toLowerCase()],
authors: [{ name: b.name }],
openGraph: {
type: "website",
locale: "en_US",
url: `https://${b.domain}`,
siteName: b.name,
title: `${b.name} - ${b.tagline}`,
description: `Enterprise blockchain infrastructure powered by ${b.name}. Multi-chain RPC, Token APIs, NFT APIs, Smart Wallets, and more.`,
},
twitter: {
card: "summary_large_image",
title: `${b.name} - ${b.tagline}`,
description: `Enterprise blockchain infrastructure powered by ${b.name}. Multi-chain RPC, Token APIs, NFT APIs, Smart Wallets, and more.`,
},
icons: {
icon: b.icon,
apple: b.icon,
shortcut: b.icon,
},
}
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
const b = getServerBrand()
return (
<html
lang="en"
suppressHydrationWarning
className={`${GeistSans.variable} ${GeistMono.variable}`}
>
<body className="min-h-screen bg-background font-sans antialiased">
<Providers>
<BrandProvider>
<AuthProvider>
<ThemeProvider
attribute="class"
defaultTheme={b.defaultTheme}
enableSystem={false}
disableTransitionOnChange
>
{children}
<Toaster />
</ThemeProvider>
</AuthProvider>
</BrandProvider>
</Providers>
</body>
</html>
)
}