perf(landing): scope Clerk provider + fix contrast/landmark on marketing page

Moves ClerkThemeProvider from the root layout into (app)/layout.tsx and
(auth)/layout.tsx — it was previously mounted globally, shipping the
~240 KiB Clerk client SDK to every route including the public marketing
page. Only Sidebar's UserButton and the (auth) sign-in/sign-up forms
actually need client-side Clerk context; server-side auth() calls are
unaffected since they read from proxy.ts's clerkMiddleware, not React
context.

Also fixes two WCAG AA contrast failures on the hero/header CTAs
(orange-500/600 -> orange-700, ~2.8:1/3.6:1 -> ~5.5:1) and adds a
missing <main> landmark around the landing page content.

Source: PageSpeed Insights audit, mobile Performance 70 -> targets >90.
This commit is contained in:
admin committed 2026-08-20 19:00:35 +02:00
1 parent bc731ac434
commit 383817916d
5 files changed
+41 -28

No files matched your search

+19 -12
View File
@@ -8,6 +8,7 @@ import { ActivePetInitializer } from "@/components/layout/ActivePetInitializer";
import { ThemeInitializer } from "@/components/layout/ThemeInitializer";
import { LegalUpdateBanner } from "@/components/legal/LegalUpdateBanner";
import { BroadcastBanner } from "@/components/broadcast/BroadcastBanner";
import { ClerkThemeProvider } from "@/components/providers/ClerkThemeProvider";
import { recordVisit } from "@/lib/visit-tracking";
/**
@@ -22,6 +23,10 @@ import { recordVisit } from "@/lib/visit-tracking";
* Security (T-2-03): pets.list scoped by ctx.userId — no cross-owner pet leakage.
*
* ActivePetProvider is already mounted in root layout.tsx so we do NOT re-wrap here.
*
* ClerkThemeProvider (client-side Clerk SDK) is scoped to this layout instead of
* root layout.tsx — only Sidebar's <UserButton/> needs it, so the public marketing
* page and legal routes no longer ship the ~240 KiB Clerk bundle (SpeedUp-Plan P0).
*/
export default async function AppLayout({
children,
@@ -69,19 +74,21 @@ export default async function AppLayout({
}
return (
<div className="flex min-h-screen">
<ActivePetInitializer firstPetId={pets[0].id} />
<ThemeInitializer dbTheme={dbTheme} />
<LegalUpdateBanner />
<BroadcastBanner />
{/* Desktop sidebar — hidden on mobile */}
<Sidebar pets={pets} />
<ClerkThemeProvider>
<div className="flex min-h-screen">
<ActivePetInitializer firstPetId={pets[0].id} />
<ThemeInitializer dbTheme={dbTheme} />
<LegalUpdateBanner />
<BroadcastBanner />
{/* Desktop sidebar — hidden on mobile */}
<Sidebar pets={pets} />
{/* Main content area */}
<main className="flex-1 overflow-auto pb-32 lg:pb-0">{children}</main>
{/* Main content area */}
<main className="flex-1 overflow-auto pb-32 lg:pb-0">{children}</main>
{/* Mobile bottom nav — hidden on desktop */}
<MobileNav pets={pets} />
</div>
{/* Mobile bottom nav — hidden on desktop */}
<MobileNav pets={pets} />
</div>
</ClerkThemeProvider>
);
}
+10 -3
View File
@@ -1,8 +1,13 @@
import { ClerkThemeProvider } from "@/components/providers/ClerkThemeProvider";
/**
* Auth layout — centers all auth pages in a 448px card.
*
* No sidebar or navigation — auth pages are standalone experiences.
* Applied to all routes under /(auth)/.
*
* ClerkThemeProvider is scoped here (not root layout.tsx) — the <SignIn>/<SignUp>
* components need it, but the public marketing page doesn't (SpeedUp-Plan P0).
*/
export default function AuthLayout({
children,
@@ -10,8 +15,10 @@ export default function AuthLayout({
children: React.ReactNode;
}) {
return (
<div className="min-h-screen flex items-center justify-center bg-background">
<div className="w-full mx-auto max-w-[448px] px-4">{children}</div>
</div>
<ClerkThemeProvider>
<div className="min-h-screen flex items-center justify-center bg-background">
<div className="w-full mx-auto max-w-[448px] px-4">{children}</div>
</div>
</ClerkThemeProvider>
);
}
+8 -11
View File
@@ -5,7 +5,6 @@ import { NextIntlClientProvider } from "next-intl";
import { getLocale, getMessages } from "next-intl/server";
import { TRPCReactProvider } from "@/trpc/client";
import { ActivePetProvider } from "@/context/ActivePetContext";
import { ClerkThemeProvider } from "@/components/providers/ClerkThemeProvider";
import { Toaster } from "@/components/ui/sonner";
import "./globals.css";
@@ -54,16 +53,14 @@ export default async function RootLayout({
>
<body className="min-h-full flex flex-col">
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
<ClerkThemeProvider>
<NextIntlClientProvider locale={locale} messages={messages}>
<TRPCReactProvider>
<ActivePetProvider>
{children}
<Toaster />
</ActivePetProvider>
</TRPCReactProvider>
</NextIntlClientProvider>
</ClerkThemeProvider>
<NextIntlClientProvider locale={locale} messages={messages}>
<TRPCReactProvider>
<ActivePetProvider>
{children}
<Toaster />
</ActivePetProvider>
</TRPCReactProvider>
</NextIntlClientProvider>
</ThemeProvider>
</body>
</html>
+3 -1
View File
@@ -43,6 +43,7 @@ export default async function RootPage() {
<div className="min-h-screen bg-background">
<LandingHeader />
<main>
{/* Hero */}
<section className="px-4 pt-8 sm:px-6 sm:pt-10">
<div className="relative mx-auto max-w-6xl overflow-hidden rounded-4xl bg-gradient-to-br from-orange-400 via-orange-500 to-orange-700">
@@ -64,7 +65,7 @@ export default async function RootPage() {
<div className="mt-10 flex flex-col justify-center gap-3 sm:flex-row lg:justify-start">
<Link
href="/sign-up"
className="inline-flex items-center justify-center rounded-xl bg-white px-7 py-3.5 text-base font-semibold text-orange-600 shadow-lg transition-transform hover:-translate-y-0.5 hover:bg-orange-50"
className="inline-flex items-center justify-center rounded-xl bg-white px-7 py-3.5 text-base font-semibold text-orange-700 shadow-lg transition-transform hover:-translate-y-0.5 hover:bg-orange-50"
>
{t("getStarted")}
</Link>
@@ -180,6 +181,7 @@ export default async function RootPage() {
</div>
</ScrollReveal>
</section>
</main>
{/* Footer — repeats the navbar */}
<LandingFooter />
+1 -1
View File
@@ -45,7 +45,7 @@ export async function LandingHeader() {
</Link>
<Link
href="/sign-up"
className="inline-flex items-center justify-center whitespace-nowrap rounded-full bg-orange-500 px-3 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-orange-600 sm:px-4"
className="inline-flex items-center justify-center whitespace-nowrap rounded-full bg-orange-700 px-3 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-orange-800 sm:px-4"
>
{t("navSignUp")}
</Link>