diff --git a/apps/code/src/components/profile/ProfileView.tsx b/apps/code/src/components/profile/ProfileView.tsx index 4c240e6ed5..57018fe84e 100644 --- a/apps/code/src/components/profile/ProfileView.tsx +++ b/apps/code/src/components/profile/ProfileView.tsx @@ -1,6 +1,7 @@ "use client"; import { Calendar, Coins, Flame, Github, Hash, Zap } from "lucide-react"; +import dynamic from "next/dynamic"; import Link from "next/link"; import { @@ -14,7 +15,6 @@ import { ProfileShareActions, XIcon, } from "@/components/profile/ProfileShareActions"; -import { ProfileTokensChart } from "@/components/profile/ProfileTokensChart"; import { ProfileViewerCta } from "@/components/profile/ProfileViewerCta"; import { ProfileWrapped } from "@/components/profile/ProfileWrapped"; import { useAppConfig } from "@/lib/config"; @@ -27,6 +27,16 @@ import type { paths } from "@/lib/api/v1"; export type ProfileData = paths["/user/profile"]["get"]["responses"][200]["content"]["application/json"]["profile"]; +// The tokens chart pulls in recharts and renders below the fold, so keep it +// out of the profile page's initial bundle. +const ProfileTokensChart = dynamic( + () => + import("@/components/profile/ProfileTokensChart").then( + (mod) => mod.ProfileTokensChart, + ), + { ssr: false, loading: () =>
}, +); + const AGENT_BY_SOURCE = new Map(); for (const agent of AGENTS) { for (const source of agent.sources) { diff --git a/apps/code/src/components/providers.tsx b/apps/code/src/components/providers.tsx index 5cf3cb1397..664e0dc931 100644 --- a/apps/code/src/components/providers.tsx +++ b/apps/code/src/components/providers.tsx @@ -5,13 +5,12 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ThemeProvider } from "next-themes"; import posthog from "posthog-js"; import { PostHogProvider } from "posthog-js/react"; -import { useEffect, useMemo } from "react"; +import { useEffect, useState } from "react"; import { Toaster } from "@/components/ui/sonner"; import { AppConfigProvider } from "@/lib/config"; import type { AppConfig } from "@/lib/config-server"; -import type { PostHogConfig } from "posthog-js"; import type { ReactNode } from "react"; interface ProvidersProps { @@ -20,7 +19,9 @@ interface ProvidersProps { } export function Providers({ children, config }: ProvidersProps) { - const queryClient = useMemo( + // useState, not useMemo: React may discard a useMemo cache, which would + // silently swap in a fresh QueryClient and drop the whole query cache. + const [queryClient] = useState( () => new QueryClient({ defaultOptions: { @@ -31,20 +32,6 @@ export function Providers({ children, config }: ProvidersProps) { }, }, }), - [], - ); - - const posthogOptions = useMemo>( - () => ({ - // Ingest through our own origin (see the /ingest rewrites in - // next.config.ts) so ad blockers that block *.posthog.com don't - // silently drop client events. - api_host: "/ingest", - ui_host: config.posthogHost, - capture_pageview: "history_change", - autocapture: true, - }), - [config.posthogHost], ); useEffect(() => { @@ -52,8 +39,20 @@ export function Providers({ children, config }: ProvidersProps) { return; } const key = config.posthogKey; + const host = config.posthogHost; + // Built inline and keyed on primitives: a memoized options object that + // React discards would re-run this effect, and its cleanup would cancel + // the already-queued init. const init = () => { - posthog.init(key, posthogOptions); + posthog.init(key, { + // Ingest through our own origin (see the /ingest rewrites in + // next.config.ts) so ad blockers that block *.posthog.com don't + // silently drop client events. + api_host: "/ingest", + ui_host: host, + capture_pageview: "history_change", + autocapture: true, + }); }; // Captures fired before init() are dropped by posthog-js, so the idle // deferral must be bounded — a busy main thread can starve @@ -64,7 +63,7 @@ export function Providers({ children, config }: ProvidersProps) { } const timer = setTimeout(init, 300); return () => clearTimeout(timer); - }, [config.posthogKey, posthogOptions]); + }, [config.posthogKey, config.posthogHost]); return ( diff --git a/apps/code/src/lib/utils/markdown.tsx b/apps/code/src/lib/utils/markdown.tsx index 9f32ec1c2d..1ddcd680bb 100644 --- a/apps/code/src/lib/utils/markdown.tsx +++ b/apps/code/src/lib/utils/markdown.tsx @@ -1,4 +1,10 @@ -import { SyntaxHighlightedPre } from "./markdown-code-block"; +import dynamic from "next/dynamic"; + +// Defer the prism-based highlighter chunk until a markdown document actually +// renders a fenced code block. +const SyntaxHighlightedPre = dynamic(() => + import("./markdown-code-block").then((mod) => mod.SyntaxHighlightedPre), +); // Get markdown component options with custom styling export function getMarkdownOptions() { diff --git a/apps/docs/app/(home)/[[...slug]]/page.tsx b/apps/docs/app/(home)/[[...slug]]/page.tsx index cf0bb14804..a77959d9d6 100644 --- a/apps/docs/app/(home)/[[...slug]]/page.tsx +++ b/apps/docs/app/(home)/[[...slug]]/page.tsx @@ -7,7 +7,6 @@ import { DocsTitle, } from "fumadocs-ui/page"; import { notFound } from "next/navigation"; -import posthog from "posthog-js"; import { LLMCopyButton, ViewOptions } from "@/components/ai/page-actions"; import { EnterpriseCTA } from "@/components/enterprise-cta"; @@ -90,6 +89,11 @@ export default async function Page(props: { // Ignore errors (rate limits, network issues, missing auth in Docker builds) } + // Left undefined when the GitHub lookup fails: fumadocs then omits the line + // entirely, which is better than stamping the build date on a page that was + // not edited — "Last updated" must only ever reflect a real content edit. + const lastUpdate = time ? new Date(time) : undefined; + const MDXContent = page.data.body; const path = page.url === "/" ? "" : page.url; @@ -120,7 +124,7 @@ export default async function Page(props: { style: "clerk", footer: , }} - lastUpdate={time ? new Date(time) : new Date()} + lastUpdate={lastUpdate} >