import type { Metadata } from 'next';
import Script from 'next/script';
import { RootProvider } from 'fumadocs-ui/provider/next';
import { LanguageProvider } from '@/components/lang/language-provider';
import './global.css';
import 'katex/dist/katex.css';
import { Geist, Space_Grotesk } from 'next/font/google';
import UtmCapture from '@/src/layouts/UtmCapture';
import SchemaInjector from '@/src/components/SchemaInjector/SchemaInjector';
import { buildWebSiteSchema } from '@/src/utils/schema-helpers';
import {
appName,
kapaConfig,
siteDescription,
siteTitle,
siteUrl,
} from '@/lib/shared';
const sans = Geist({
subsets: ['latin'],
variable: '--font-sans',
display: 'swap',
});
const heading = Space_Grotesk({
subsets: ['latin'],
weight: ['300', '400', '500', '600', '700'],
variable: '--font-heading',
display: 'swap',
});
const disabledSearchHotKey = [
{
key: "__disabled__",
display: null,
},
];
// `%s` template mirrors Docusaurus' default `
` format so every
// SERP entry still reads "Page Title | {siteTitle}".
//
// `openGraph` / `twitter` defaults here set the site-wide baseline that
// every section inherits (Next's `generateMetadata` deep-merges onto
// this object). Per-page routes can override individual fields — the
// blog section adds `openGraph.type = 'article'` + publishedTime, the
// docs section adds per-page OG images, etc.
export const metadata: Metadata = {
metadataBase: new URL(siteUrl),
title: {
default: siteTitle,
template: `%s | ${siteTitle}`,
},
description: siteDescription,
alternates: { canonical: '/' },
openGraph: {
type: 'website',
siteName: appName,
url: siteUrl,
title: siteTitle,
description: siteDescription,
// Site-wide fallback preview image. Every section/page inherits
// this unless it overrides `openGraph.images` (the docs section
// does, swapping in a per-page `/og/docs/.../image.png`). Mirrors
// the old Docusaurus `themeConfig.image = 'img/social_card.png'`
// default so guides/tutorials/integrations/blog/changelog/home
// never end up with a blank link preview on social shares.
images: '/img/social_card.png',
},
twitter: {
card: 'summary_large_image',
site: '@deepeval',
creator: '@deepeval',
title: siteTitle,
description: siteDescription,
// Deliberately no `images:` here — we rely on X's documented
// fallback to `og:image` when `twitter:image` is absent. Setting
// it explicitly would stick the generic social card even on
// docs pages whose `og:image` is overridden per-page (Next
// replaces the whole `twitter` block across nested
// `generateMetadata` calls instead of deep-merging, so a section
// override of just `twitter.images` would clobber the `card` /
// `site` / `creator` fields here). LinkedIn / Slack / Discord /
// Facebook read `og:image` directly, so the single `og:image`
// source-of-truth covers every surface.
},
};
// Organization schema mirrored from the old Docusaurus `headTags` block
// (docusaurus.config.ts:161-181). Rendered once in via the App
// Router layout — Next will keep JSON-LD scripts where they are placed.
const organizationJsonLd = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Confident AI Inc.',
alternateName: 'Confident AI',
url: siteUrl,
logo: `${siteUrl}/icons/DeepEval.svg`,
sameAs: [
'https://github.com/confident-ai/deepeval',
'https://x.com/deepeval',
'https://discord.gg/a3K9c8GRGt',
'https://www.reddit.com/r/deepeval/',
],
};
export default function Layout({ children }: LayoutProps<'/'>) {
return (
{/*
Two site-wide JSON-LD blocks rendered once per page:
`Organization` (mirrored from the old Docusaurus `headTags`)
and `WebSite` (so crawlers have a canonical top-level entity
to hang everything else off of). Both use the shared
`SchemaInjector` helper, which safely escapes `` inside the
serialized JSON.
*/}
{/*
Kapa.ai "Ask AI" widget.
Deliberately rendered as a native `
);
}