From fa4e8828435b68f99097f5a3ccbf9c55e4d29277 Mon Sep 17 00:00:00 2001 From: Taesu <166604494+bytaesu@users.noreply.github.com> Date: Tue, 3 Mar 2026 05:24:38 +0900 Subject: [PATCH] docs: migrate products page from query params to sub-routes (#8299) --- landing/app/enterprise/enterprise-client.tsx | 2 +- .../[tab]/_components/framework-content.tsx | 156 ++++++ .../_components/infrastructure-content.tsx} | 447 +----------------- .../[tab]/_components/products-shell.tsx | 264 +++++++++++ landing/app/products/[tab]/layout.tsx | 13 + landing/app/products/[tab]/page.tsx | 47 ++ landing/app/products/page.tsx | 16 +- landing/components/landing/hero-readme.tsx | 6 +- .../landing/staggered-nav-files.tsx | 4 +- landing/next.config.js | 13 + 10 files changed, 503 insertions(+), 465 deletions(-) create mode 100644 landing/app/products/[tab]/_components/framework-content.tsx rename landing/app/products/{products-client.tsx => [tab]/_components/infrastructure-content.tsx} (51%) create mode 100644 landing/app/products/[tab]/_components/products-shell.tsx create mode 100644 landing/app/products/[tab]/layout.tsx create mode 100644 landing/app/products/[tab]/page.tsx diff --git a/landing/app/enterprise/enterprise-client.tsx b/landing/app/enterprise/enterprise-client.tsx index 1605e7d275..ff099ef6cd 100644 --- a/landing/app/enterprise/enterprise-client.tsx +++ b/landing/app/enterprise/enterprise-client.tsx @@ -66,7 +66,7 @@ function EnterpriseHero() { {/* CTA */}
View Products diff --git a/landing/app/products/[tab]/_components/framework-content.tsx b/landing/app/products/[tab]/_components/framework-content.tsx new file mode 100644 index 0000000000..15d28ff83c --- /dev/null +++ b/landing/app/products/[tab]/_components/framework-content.tsx @@ -0,0 +1,156 @@ +"use client"; + +import { motion } from "framer-motion"; +import Link from "next/link"; + +export function FrameworkContent() { + const frameworkFeatures = [ + { + category: "Authentication", + items: [ + "Email & password", + "Social sign-in (OAuth)", + "Magic link / OTP", + "Passkeys (WebAuthn)", + "Multi-factor authentication", + "Anonymous sessions", + ], + }, + { + category: "Authorization", + items: [ + "Role-based access control", + "Organization / teams", + "Admin management", + "Session management", + "Account linking", + "Impersonation", + ], + }, + { + category: "Platform", + items: [ + "Framework agnostic", + "20+ database adapters", + "TypeScript-first", + "Edge runtime support", + "Rate limiting", + "CSRF protection", + ], + }, + ]; + + return ( +
+ +

+ Everything you need to build production-ready auth. Free and open + source under the MIT license. +

+ +
+
+
+
+
+

+ Community +

+ + free forever + +
+
    + {[ + "Unlimited users", + "All auth features", + "50+ plugins", + "Self-hosted", + "MIT license", + ].map((item) => ( +
  • + + + + + {item} +
  • + ))} +
+
+
+ + + Read Docs + + + + + +
+
+
+
+
+ + +

+ # what's included +

+ +
+ {frameworkFeatures.map((group, gi) => ( + +

+ {group.category} +

+
    + {group.items.map((item) => ( +
  • + + + + + {item} +
  • + ))} +
+
+ ))} +
+
+
+ ); +} diff --git a/landing/app/products/products-client.tsx b/landing/app/products/[tab]/_components/infrastructure-content.tsx similarity index 51% rename from landing/app/products/products-client.tsx rename to landing/app/products/[tab]/_components/infrastructure-content.tsx index c53fa150e3..23b9b054f3 100644 --- a/landing/app/products/products-client.tsx +++ b/landing/app/products/[tab]/_components/infrastructure-content.tsx @@ -1,16 +1,10 @@ "use client"; -import { AnimatePresence, motion } from "framer-motion"; +import { motion } from "framer-motion"; import { Check, Minus } from "lucide-react"; -import Link from "next/link"; -import { useSearchParams } from "next/navigation"; -import { useEffect, useState } from "react"; -import { HalftoneBackground } from "@/components/landing/halftone-bg"; const ctaHref = `https://dash.better-auth.com/sign-in?callbackUrl=${encodeURIComponent("/?redirectTo=/settings/billing")}`; -type Tab = "framework" | "infrastructure"; - const pricingTiers = [ { name: "Starter", @@ -260,193 +254,6 @@ function ComparisonCell({ value }: { value: string | boolean }) { ); } -function FrameworkHero() { - const highlights = [{ label: "License", value: "MIT" }]; - - return ( - -
-
-

- - Open Source - -

-

- A comprehensive, framework-agnostic authentication library for - TypeScript. Free and open source under the MIT license. -

-
- -
- {highlights.map((item, i) => ( - - - {item.label} - - - {item.value} - - - ))} -
- -
- - Read Docs - - - GitHub - - - - -
-
-
- ); -} - -function InfrastructureHero() { - const principles = [ - { label: "Framework", value: "Open source" }, - { label: "Users", value: "Unlimited" }, - { label: "Audit logs", value: "From 10k/mo" }, - { label: "Security", value: "$0.0001/event overage" }, - { label: "SSO", value: "Business+" }, - ]; - - const tiers = [ - { name: "Starter", price: "$0", note: "/mo" }, - { name: "Pro", price: "$20", note: "/mo", highlight: true }, - { name: "Business", price: "$299", note: "/mo" }, - { name: "Enterprise", price: "Custom", note: null }, - ]; - - return ( - -
-
-

- - Infrastructure - -

-

- Dashboard, security, audit logs, and more for your auth. -

-
- -
- {tiers.map((tier) => ( -
-

- {tier.name} -

-

- {tier.price} - {tier.note && ( - - {tier.note} - - )} -

-
- ))} -
- -
- {principles.map((item, i) => ( - - - {item.label} - - - {item.value} - - - ))} -
- -
- - Get Started - - - Contact Sales - - - - -
-
-
- ); -} - function PricingCard({ tier, index, @@ -541,159 +348,7 @@ function PricingCard({ ); } -function FrameworkContent() { - const frameworkFeatures = [ - { - category: "Authentication", - items: [ - "Email & password", - "Social sign-in (OAuth)", - "Magic link / OTP", - "Passkeys (WebAuthn)", - "Multi-factor authentication", - "Anonymous sessions", - ], - }, - { - category: "Authorization", - items: [ - "Role-based access control", - "Organization / teams", - "Admin management", - "Session management", - "Account linking", - "Impersonation", - ], - }, - { - category: "Platform", - items: [ - "Framework agnostic", - "20+ database adapters", - "TypeScript-first", - "Edge runtime support", - "Rate limiting", - "CSRF protection", - ], - }, - ]; - - return ( -
- -

- Everything you need to build production-ready auth. Free and open - source under the MIT license. -

- -
-
-
-
-
-

- Community -

- - free forever - -
-
    - {[ - "Unlimited users", - "All auth features", - "50+ plugins", - "Self-hosted", - "MIT license", - ].map((item) => ( -
  • - - + - - {item} -
  • - ))} -
-
-
- - - Read Docs - - - - - -
-
-
-
-
- - -

- # what's included -

- -
- {frameworkFeatures.map((group, gi) => ( - -

- {group.category} -

-
    - {group.items.map((item) => ( -
  • - - + - - {item} -
  • - ))} -
-
- ))} -
-
-
- ); -} - -function InfrastructureContent() { +export function InfrastructureContent() { const tierKeys = ["starter", "pro", "business", "enterprise"] as const; const tierLabels = ["Starter", "Pro", "Business", "Enterprise"]; @@ -933,101 +588,3 @@ function InfrastructureContent() {
); } - -export function ProductsPageClient() { - const searchParams = useSearchParams(); - const tabParam = searchParams.get("tab"); - const initialTab: Tab = - tabParam === "infrastructure" ? "infrastructure" : "framework"; - const [activeTab, setActiveTab] = useState(initialTab); - - useEffect(() => { - const next: Tab = - tabParam === "infrastructure" ? "infrastructure" : "framework"; - setActiveTab(next); - }, [tabParam]); - - return ( -
-
-
- {/* Left side — Hero */} -
-
- -
- - {activeTab === "framework" ? ( - - - - ) : ( - - - - )} - -
- - {/* Right side — Content */} -
-
- - - {activeTab === "framework" - ? "Better Auth Framework" - : "Infrastructure"} - - -
- - - {activeTab === "framework" ? ( - - - - ) : ( - - - - )} - -
-
-
-
- ); -} diff --git a/landing/app/products/[tab]/_components/products-shell.tsx b/landing/app/products/[tab]/_components/products-shell.tsx new file mode 100644 index 0000000000..b6c136621e --- /dev/null +++ b/landing/app/products/[tab]/_components/products-shell.tsx @@ -0,0 +1,264 @@ +"use client"; + +import { AnimatePresence, motion } from "framer-motion"; +import Link from "next/link"; +import { HalftoneBackground } from "@/components/landing/halftone-bg"; + +function FrameworkHero() { + const highlights = [{ label: "License", value: "MIT" }]; + + return ( + +
+
+

+ + Open Source + +

+

+ A comprehensive, framework-agnostic authentication library for + TypeScript. Free and open source under the MIT license. +

+
+ +
+ {highlights.map((item, i) => ( + + + {item.label} + + + {item.value} + + + ))} +
+ +
+ + Read Docs + + + GitHub + + + + +
+
+
+ ); +} + +function InfrastructureHero() { + const principles = [ + { label: "Framework", value: "Open source" }, + { label: "Users", value: "Unlimited" }, + { label: "Audit logs", value: "From 10k/mo" }, + { label: "Security", value: "$0.0001/event overage" }, + { label: "SSO", value: "Business+" }, + ]; + + const tiers = [ + { name: "Starter", price: "$0", note: "/mo" }, + { name: "Pro", price: "$20", note: "/mo", highlight: true }, + { name: "Business", price: "$299", note: "/mo" }, + { name: "Enterprise", price: "Custom", note: null }, + ]; + + return ( + +
+
+

+ + Infrastructure + +

+

+ Dashboard, security, audit logs, and more for your auth. +

+
+ +
+ {tiers.map((tier) => ( +
+

+ {tier.name} +

+

+ {tier.price} + {tier.note && ( + + {tier.note} + + )} +

+
+ ))} +
+ +
+ {principles.map((item, i) => ( + + + {item.label} + + + {item.value} + + + ))} +
+ +
+ + Get Started + + + Contact Sales + + + + +
+
+
+ ); +} + +export function ProductsShell({ + tab, + children, +}: { + tab: string; + children: React.ReactNode; +}) { + const isInfrastructure = tab === "infrastructure"; + + return ( +
+
+
+ {/* Left side — Hero */} +
+
+ +
+ + {isInfrastructure ? ( + + + + ) : ( + + + + )} + +
+ + {/* Right side — Content */} +
+
+ + + {isInfrastructure + ? "Infrastructure" + : "Better Auth Framework"} + + +
+ + {children} +
+
+
+
+ ); +} diff --git a/landing/app/products/[tab]/layout.tsx b/landing/app/products/[tab]/layout.tsx new file mode 100644 index 0000000000..65289569e8 --- /dev/null +++ b/landing/app/products/[tab]/layout.tsx @@ -0,0 +1,13 @@ +import { ProductsShell } from "./_components/products-shell"; + +export default async function TabLayout({ + children, + params, +}: { + children: React.ReactNode; + params: Promise<{ tab: string }>; +}) { + const { tab } = await params; + + return {children}; +} diff --git a/landing/app/products/[tab]/page.tsx b/landing/app/products/[tab]/page.tsx new file mode 100644 index 0000000000..fbede66619 --- /dev/null +++ b/landing/app/products/[tab]/page.tsx @@ -0,0 +1,47 @@ +import type { Metadata } from "next"; +import { redirect } from "next/navigation"; +import { FrameworkContent } from "./_components/framework-content"; +import { InfrastructureContent } from "./_components/infrastructure-content"; + +const tabs = { + framework: { + title: "Framework — Products", + description: + "Better Auth — a comprehensive, framework-agnostic authentication library for TypeScript. Free and open source under the MIT license.", + }, + infrastructure: { + title: "Infrastructure — Products", + description: + "Better Auth Infrastructure — dashboard, audit logs, security detection, and more for your auth.", + }, +} as const; + +type Tab = keyof typeof tabs; + +export function generateStaticParams() { + return Object.keys(tabs).map((tab) => ({ tab })); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ tab: string }>; +}): Promise { + const { tab } = await params; + const meta = tabs[tab as Tab]; + if (!meta) return {}; + return { title: meta.title, description: meta.description }; +} + +export default async function TabPage({ + params, +}: { + params: Promise<{ tab: string }>; +}) { + const { tab } = await params; + + if (tab === "framework") return ; + if (tab === "infrastructure") return ; + + redirect("/products/framework"); +} diff --git a/landing/app/products/page.tsx b/landing/app/products/page.tsx index eff57241f2..4ebf335f46 100644 --- a/landing/app/products/page.tsx +++ b/landing/app/products/page.tsx @@ -1,17 +1,5 @@ -import type { Metadata } from "next"; -import { Suspense } from "react"; -import { ProductsPageClient } from "./products-client"; - -export const metadata: Metadata = { - title: "Products", - description: - "Better Auth — free open-source framework with optional managed infrastructure.", -}; +import { redirect } from "next/navigation"; export default function ProductsPage() { - return ( - - - - ); + redirect("/products/framework"); } diff --git a/landing/components/landing/hero-readme.tsx b/landing/components/landing/hero-readme.tsx index 809244b39b..2eeee0a75e 100644 --- a/landing/components/landing/hero-readme.tsx +++ b/landing/components/landing/hero-readme.tsx @@ -1621,7 +1621,7 @@ export function HeroReadMe({ headline: "Security & observability.", desc: "Bot detection, real-time behavior analysis, IP blocking, email validation, and more.", security: true, - href: "/products?tab=infrastructure", + href: "/products/infrastructure", managed: true, }, { @@ -1629,7 +1629,7 @@ export function HeroReadMe({ headline: "User management.", desc: "Manage users, sessions, and organizations. Track sign-ups, active users, and growth.", dashboard: true, - href: "/products?tab=infrastructure", + href: "/products/infrastructure", managed: true, }, ].map((feature, i) => ( @@ -2499,7 +2499,7 @@ export function HeroReadMe({

diff --git a/landing/components/landing/staggered-nav-files.tsx b/landing/components/landing/staggered-nav-files.tsx index b8c9636f70..89d454fd6d 100644 --- a/landing/components/landing/staggered-nav-files.tsx +++ b/landing/components/landing/staggered-nav-files.tsx @@ -26,8 +26,8 @@ const navFiles: NavFileItem[] = [ ]; const productFiles: NavFileItem[] = [ - { name: "framework", href: "/products?tab=framework" }, - { name: "infrastructure", href: "/products?tab=infrastructure" }, + { name: "framework", href: "/products/framework" }, + { name: "infrastructure", href: "/products/infrastructure" }, ]; const resourceFiles: NavFileItem[] = [ diff --git a/landing/next.config.js b/landing/next.config.js index 88533d4b3a..de2db9bbc2 100644 --- a/landing/next.config.js +++ b/landing/next.config.js @@ -38,6 +38,19 @@ const nextConfig = { destination: "/docs/introduction", permanent: false, }, + // Legacy query string based redirects + { + source: "/products", + has: [{ type: "query", key: "tab", value: "framework" }], + destination: "/products/framework", + permanent: true, + }, + { + source: "/products", + has: [{ type: "query", key: "tab", value: "infrastructure" }], + destination: "/products/infrastructure", + permanent: true, + }, ]; }, };