From 12f16c371ee47615dcecbbf30afb1cbb638aa71b Mon Sep 17 00:00:00 2001 From: Sandy Date: Mon, 9 Mar 2026 18:18:53 -0300 Subject: [PATCH] docs(metadata): centralize SEO metadata with createMetadata helper (#8486) --- landing/app/blog/[[...slug]]/page.tsx | 9 ++--- landing/app/blog/layout.tsx | 29 ++++++++++++----- landing/app/careers/page.tsx | 5 +-- landing/app/changelog/page.tsx | 7 ++-- landing/app/community/page.tsx | 5 +-- landing/app/docs/[[...slug]]/page.tsx | 5 +-- landing/app/enterprise/page.tsx | 5 +-- landing/app/layout.tsx | 35 ++------------------ landing/app/products/[tab]/page.tsx | 3 +- landing/lib/metadata.ts | 47 +++++++++++++++++++++++++++ landing/lib/rss.ts | 2 +- landing/lib/utils.ts | 8 ----- 12 files changed, 95 insertions(+), 65 deletions(-) create mode 100644 landing/lib/metadata.ts diff --git a/landing/app/blog/[[...slug]]/page.tsx b/landing/app/blog/[[...slug]]/page.tsx index e3d99cdb84..24f3f76ee0 100644 --- a/landing/app/blog/[[...slug]]/page.tsx +++ b/landing/app/blog/[[...slug]]/page.tsx @@ -7,6 +7,7 @@ import Link from "next/link"; import { notFound } from "next/navigation"; import { BlogLeftPanel } from "@/components/blog/blog-left-panel"; import { Callout } from "@/components/ui/callout"; +import { createMetadata } from "@/lib/metadata"; import { blogs } from "@/lib/source"; import { cn } from "@/lib/utils"; @@ -270,10 +271,10 @@ export async function generateMetadata({ }) { const { slug } = await params; if (!slug) { - return { + return createMetadata({ title: "Blog - Better Auth", description: "Latest updates, articles, and insights about Better Auth", - }; + }); } const page = blogs.getPage(slug); if (!page || page.data.draft) return notFound(); @@ -296,7 +297,7 @@ export async function generateMetadata({ const ogImage = image || ogUrl; - return { + return createMetadata({ title, description, openGraph: { @@ -311,7 +312,7 @@ export async function generateMetadata({ description, images: [ogImage], }, - }; + }); } export function generateStaticParams() { diff --git a/landing/app/blog/layout.tsx b/landing/app/blog/layout.tsx index a4aea65ce8..229959e9ce 100644 --- a/landing/app/blog/layout.tsx +++ b/landing/app/blog/layout.tsx @@ -1,21 +1,34 @@ import { RootProvider } from "fumadocs-ui/provider/next"; import type { Metadata } from "next"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - title: "Blog - Better Auth", - description: "Latest updates, articles, and insights about Better Auth", +const description = "Latest updates, articles, and insights about Better Auth"; + +export const metadata: Metadata = createMetadata({ + title: "Blog", + description, openGraph: { + url: "/blog", title: "Blog - Better Auth", - description: "Latest updates, articles, and insights about Better Auth", + description, images: ["/api/og-release?heading=Better%20Auth%20Blog"], }, twitter: { - card: "summary_large_image", - title: "Blog - Better Auth", - description: "Latest updates, articles, and insights about Better Auth", images: ["/api/og-release?heading=Better%20Auth%20Blog"], + title: "Blog - Better Auth", + description, }, -}; + alternates: { + types: { + "application/rss+xml": [ + { + title: "Better Auth Blog", + url: "https://better-auth.com/blog/rss.xml", + }, + ], + }, + }, +}); export default function BlogLayout({ children, diff --git a/landing/app/careers/page.tsx b/landing/app/careers/page.tsx index 3f8eabcc66..c08e17ba97 100644 --- a/landing/app/careers/page.tsx +++ b/landing/app/careers/page.tsx @@ -1,10 +1,11 @@ import type { Metadata } from "next"; +import { createMetadata } from "@/lib/metadata"; import { CareersPageClient } from "./careers-client"; -export const metadata: Metadata = { +export const metadata: Metadata = createMetadata({ title: "Careers", description: "Join the Better Auth team — open positions and how to apply.", -}; +}); export default function CareersPage() { return ; diff --git a/landing/app/changelog/page.tsx b/landing/app/changelog/page.tsx index 57090d91ef..b9c9ebfcf6 100644 --- a/landing/app/changelog/page.tsx +++ b/landing/app/changelog/page.tsx @@ -1,5 +1,6 @@ import Link from "next/link"; import { HalftoneBackground } from "@/components/landing/halftone-bg"; +import { createMetadata } from "@/lib/metadata"; import { ChangelogContent } from "./changelog-content"; export const dynamic = "force-static"; @@ -148,7 +149,7 @@ export default async function ChangelogPage() { ); } -export const metadata = { - title: "Changelog - Better Auth", +export const metadata = createMetadata({ + title: "Changelog", description: "Latest changes, fixes, and updates to Better Auth", -}; +}); diff --git a/landing/app/community/page.tsx b/landing/app/community/page.tsx index 87381810be..0812e0dc7c 100644 --- a/landing/app/community/page.tsx +++ b/landing/app/community/page.tsx @@ -1,12 +1,13 @@ import type { Metadata } from "next"; import { getCommunityStats } from "@/lib/community-stats"; +import { createMetadata } from "@/lib/metadata"; import { CommunityPageClient } from "./community-client"; -export const metadata: Metadata = { +export const metadata: Metadata = createMetadata({ title: "Community", description: "Join the Better Auth community — contributors, Discord members, and ecosystem stats.", -}; +}); export const revalidate = 21600; // Revalidate every 6 hours diff --git a/landing/app/docs/[[...slug]]/page.tsx b/landing/app/docs/[[...slug]]/page.tsx index c8a48c0fe6..902f7853bb 100644 --- a/landing/app/docs/[[...slug]]/page.tsx +++ b/landing/app/docs/[[...slug]]/page.tsx @@ -24,6 +24,7 @@ import { GenerateSecret, } from "@/components/docs/mdx-components"; import { Callout } from "@/components/ui/callout"; +import { createMetadata } from "@/lib/metadata"; import { getSource } from "@/lib/source"; import { cn } from "@/lib/utils"; import { LLMCopyButton, ViewOptions } from "./page.client"; @@ -164,7 +165,7 @@ export async function generateMetadata({ const ogUrl = `/api/og?${ogSearchParams.toString()}`; - return { + return createMetadata({ title: page.data.title, description: page.data.description, openGraph: { @@ -186,5 +187,5 @@ export async function generateMetadata({ description: page.data.description, images: [ogUrl], }, - }; + }); } diff --git a/landing/app/enterprise/page.tsx b/landing/app/enterprise/page.tsx index dacddd4c05..d5e56aa0a6 100644 --- a/landing/app/enterprise/page.tsx +++ b/landing/app/enterprise/page.tsx @@ -1,11 +1,12 @@ import type { Metadata } from "next"; +import { createMetadata } from "@/lib/metadata"; import { EnterprisePageClient } from "./enterprise-client"; -export const metadata: Metadata = { +export const metadata: Metadata = createMetadata({ title: "Enterprise", description: "Better Auth for enterprise — SSO, SAML, audit logs, and dedicated support.", -}; +}); export default function EnterprisePage() { return ; diff --git a/landing/app/layout.tsx b/landing/app/layout.tsx index 8d78b488b9..25b3466f9a 100644 --- a/landing/app/layout.tsx +++ b/landing/app/layout.tsx @@ -7,6 +7,7 @@ import Script from "next/script"; import type { ReactNode } from "react"; import { StaggeredNavFiles } from "@/components/landing/staggered-nav-files"; import { Providers } from "@/components/providers"; +import { createMetadata } from "@/lib/metadata"; const fontSans = Geist({ subsets: ["latin"], @@ -18,43 +19,13 @@ const fontMono = Geist_Mono({ variable: "--font-mono", }); -export const metadata: Metadata = { - metadataBase: new URL( - process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` - : process.env.NODE_ENV === "production" - ? "https://better-auth.com" - : (process.env.NEXT_PUBLIC_URL ?? "http://localhost:3000"), - ), +export const metadata: Metadata = createMetadata({ title: { template: "%s | Better Auth", default: "Better Auth", }, description: "The Most Comprehensive Authentication Framework", - icons: { - icon: [ - { url: "/favicon/favicon.ico", sizes: "any" }, - { - url: "/favicon/favicon-32x32.png", - sizes: "32x32", - type: "image/png", - }, - { - url: "/favicon/favicon-16x16.png", - sizes: "16x16", - type: "image/png", - }, - ], - apple: "/favicon/apple-touch-icon.png", - }, - openGraph: { - images: ["/og.png"], - }, - twitter: { - card: "summary_large_image", - images: ["/og.png"], - }, -}; +}); export default function RootLayout({ children }: { children: ReactNode }) { return ( diff --git a/landing/app/products/[tab]/page.tsx b/landing/app/products/[tab]/page.tsx index fbede66619..8c75ed7481 100644 --- a/landing/app/products/[tab]/page.tsx +++ b/landing/app/products/[tab]/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { redirect } from "next/navigation"; +import { createMetadata } from "@/lib/metadata"; import { FrameworkContent } from "./_components/framework-content"; import { InfrastructureContent } from "./_components/infrastructure-content"; @@ -30,7 +31,7 @@ export async function generateMetadata({ const { tab } = await params; const meta = tabs[tab as Tab]; if (!meta) return {}; - return { title: meta.title, description: meta.description }; + return createMetadata({ title: meta.title, description: meta.description }); } export default async function TabPage({ diff --git a/landing/lib/metadata.ts b/landing/lib/metadata.ts new file mode 100644 index 0000000000..ae0a90fa2f --- /dev/null +++ b/landing/lib/metadata.ts @@ -0,0 +1,47 @@ +import type { Metadata } from "next"; + +export function createMetadata(override: Metadata): Metadata { + return { + ...override, + metadataBase: baseUrl, + openGraph: { + title: override.title ?? undefined, + description: override.description ?? undefined, + url: "https://better-auth.com", + images: "/og.png", + siteName: "Better Auth", + ...override.openGraph, + }, + twitter: { + card: "summary_large_image", + title: override.title ?? undefined, + description: override.description ?? undefined, + images: "/og.png", + ...override.twitter, + }, + icons: { + icon: [ + { url: "/favicon/favicon.ico", sizes: "any" }, + { + url: "/favicon/favicon-32x32.png", + sizes: "32x32", + type: "image/png", + }, + { + url: "/favicon/favicon-16x16.png", + sizes: "16x16", + type: "image/png", + }, + ], + apple: "/favicon/apple-touch-icon.png", + }, + }; +} + +export const baseUrl = + process.env.NODE_ENV === "development" || + (!process.env.VERCEL_PROJECT_PRODUCTION_URL && !process.env.VERCEL_URL) + ? new URL("http://localhost:3000") + : new URL( + `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL || process.env.VERCEL_URL}`, + ); diff --git a/landing/lib/rss.ts b/landing/lib/rss.ts index 93325ba1d9..85ebafd720 100644 --- a/landing/lib/rss.ts +++ b/landing/lib/rss.ts @@ -1,6 +1,6 @@ import { Feed } from "feed"; +import { baseUrl } from "./metadata"; import { blogs } from "./source"; -import { baseUrl } from "./utils"; export function getRSS() { const feed = new Feed({ diff --git a/landing/lib/utils.ts b/landing/lib/utils.ts index 8a90058be7..7d63e21d50 100644 --- a/landing/lib/utils.ts +++ b/landing/lib/utils.ts @@ -19,11 +19,3 @@ export function mergeRefs( }); }; } - -export const baseUrl = - process.env.NODE_ENV === "development" || - (!process.env.VERCEL_PROJECT_PRODUCTION_URL && !process.env.VERCEL_URL) - ? new URL("http://localhost:3000") - : new URL( - `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL || process.env.VERCEL_URL}`, - );