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:
1 parent
bc731ac434
commit
383817916d
5 files changed
+41
-28
No files matched your search
+19
-12
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user