From dd63c0974f25ee1f3d33bbe63f5714ccf277950a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Mar 2026 16:29:37 +0000 Subject: [PATCH 1/3] Improve app loading speed by eliminating blank screen and reducing bundle size - Replace blank screen (null) in ClientOnly with a loading spinner so users see immediate visual feedback during hydration - Show themed loading spinner in AppProviderWrapper while Apollo client initializes instead of rendering nothing - Lazy-load PostHog analytics via dynamic import() to remove ~100KB from the critical rendering path - Dynamic import VoronoiBackgroundDefault on home page to defer loading the Delaunator geometry library - Remove unused imports: lodash from _baseLayout and useRsnUser, async-mutex from _baseLayout, useEffectDeepEqual from useRsnUser - Remove three empty useEffectDeepEqual hooks in useRsnUser that ran on every state change but only contained commented-out console.debug calls - Cache browser timezone string at module level to avoid repeated Intl.DateTimeFormat() lookups during auth flow https://claude.ai/code/session_01VaJKr53J9VeZTBUsuWSXxL --- apps/next-main/app/app/page.page.tsx | 9 +- apps/next-main/clientOnly/hooks/useRsnUser.ts | 24 ++--- apps/next-main/components/ClientOnly.tsx | 34 ++++++- .../next-main/components/_APP/_baseLayout.tsx | 93 +++++++++++-------- 4 files changed, 100 insertions(+), 60 deletions(-) diff --git a/apps/next-main/app/app/page.page.tsx b/apps/next-main/app/app/page.page.tsx index 8a967f4..99e1a56 100644 --- a/apps/next-main/app/app/page.page.tsx +++ b/apps/next-main/app/app/page.page.tsx @@ -7,11 +7,11 @@ import React, { } from "react"; import {motion} from "framer-motion"; -import _lodash from "lodash"; import { ChevronDown, FileText, } from "lucide-react"; +import dynamic from "next/dynamic"; import {useRouter} from "next/navigation"; import posthog from "posthog-js"; @@ -31,12 +31,15 @@ import { useTheme, } from "@mui/material"; -import VoronoiBackgroundDefault - from "../../components/backgrounds/VoronoiBackgroundDefault"; import {Footer} from "../../components/footer/Footer"; import {HomeMainSkillCreatorV2} from "../HomeMainSkillCreatorV2"; import {HomepageContinueLearning} from "../HomepageContinueLearning"; +const VoronoiBackgroundDefault = dynamic( + () => import("../../components/backgrounds/VoronoiBackgroundDefault"), + { ssr: false } +); + const AnimatedLearnMore = () => { const durationsMs = useDurationsMs(); const theme = useTheme(); diff --git a/apps/next-main/clientOnly/hooks/useRsnUser.ts b/apps/next-main/clientOnly/hooks/useRsnUser.ts index 77084b7..d8fabd0 100644 --- a/apps/next-main/clientOnly/hooks/useRsnUser.ts +++ b/apps/next-main/clientOnly/hooks/useRsnUser.ts @@ -1,8 +1,6 @@ "use client"; import {useState} from "react"; -import _ from "lodash"; - import {useSupabase} from "@/components/supabase/SupabaseProvider"; import {useReactiveVar} from "@apollo/client"; import { @@ -10,7 +8,6 @@ import { useRsnUserSysdataFlatFragLoader, } from "@reasonote/lib-sdk-apollo-client-react"; import {uuidv4} from "@reasonote/lib-utils"; -import {useEffectDeepEqual} from "@reasonote/lib-utils-frontend"; import {useSupabaseSession} from "../../components/supabase/useSupabaseSession"; import {useSupabaseUser} from "../../components/supabase/useSupabaseUser"; @@ -22,6 +19,11 @@ interface LoginJwtResult { has_password: boolean; } +// Cache the timezone string to avoid repeated Intl lookups +const browserTimezone = typeof Intl !== 'undefined' + ? Intl.DateTimeFormat().resolvedOptions().timeZone + : 'UTC'; + export function useRsnUserId() { const rsnUserId = useReactiveVar(rsnUserIdVar); return rsnUserId; @@ -53,7 +55,7 @@ export function useRsnUser() { try { console.debug('login_jwt fetchFn'); var innerJwtResult = await supabase.rpc('login_jwt', { - browser_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone + browser_timezone: browserTimezone }); if (innerJwtResult.error) { @@ -67,7 +69,7 @@ export function useRsnUser() { await supabase.auth.signInAnonymously(); // Refetch the jwt with the new anonymous user innerJwtResult = await supabase.rpc('login_jwt', { - browser_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone + browser_timezone: browserTimezone }); } else { @@ -98,18 +100,6 @@ export function useRsnUser() { currentRsnUserId ? `rsnusrsys_${currentRsnUserId?.split("_")[1]}` : undefined ); - useEffectDeepEqual(() => { - // console.debug("useRsnUser: rsnUserSysdata", rsnUserSysdata); - }, [rsnUserSysdata.data]); - - useEffectDeepEqual(() => { - // console.debug("useRsnUser: rsnUser", rsnUser); - }, [rsnUser.data]); - - useEffectDeepEqual(() => { - // console.debug(`${instanceId}: login_jwt useRsnUser: rsnUserId`, currentRsnUserId); - }, [currentRsnUserId]); - return { refresh: loginJwtResult.refetch, sbSession: session, diff --git a/apps/next-main/components/ClientOnly.tsx b/apps/next-main/components/ClientOnly.tsx index 368f716..3e6b3bf 100644 --- a/apps/next-main/components/ClientOnly.tsx +++ b/apps/next-main/components/ClientOnly.tsx @@ -1,12 +1,42 @@ "use client"; import { useEffect, useState } from "react"; -export default function ClientOnly({ children, ...delegated }: any) { +export default function ClientOnly({ children, fallback, ...delegated }: any) { const [hasMounted, setHasMounted] = useState(false); useEffect(() => { setHasMounted(true); }, []); - return hasMounted ? children : null; + if (!hasMounted) { + return fallback ?? ( +
+
+ +
+ ); + } + + return children; } diff --git a/apps/next-main/components/_APP/_baseLayout.tsx b/apps/next-main/components/_APP/_baseLayout.tsx index b9e8ff7..f1f8fc1 100644 --- a/apps/next-main/components/_APP/_baseLayout.tsx +++ b/apps/next-main/components/_APP/_baseLayout.tsx @@ -8,12 +8,8 @@ import React, { useState, } from "react"; -//////////////////////////////////////////////////////// -// When the user changes, we must update the subscription var. -import * as AsyncMutex from "async-mutex"; import {UserInteractionProvider} from "contexts/UserInteractionContext"; -import _ from "lodash"; -import posthog from "posthog-js"; +import type PostHogType from "posthog-js"; import {AIBrowserProvider} from "@/clientOnly/ai/AIBrowserProvider"; import {BreadcrumbProvider} from "@/clientOnly/context/BreadcrumbContext"; @@ -107,12 +103,38 @@ function AppProviderWrapper({ children }: React.PropsWithChildren<{}>) { }); }, [sbUrl]); - // // Create a new supabase browser client on every first render. - // const [supabaseClient] = useState(() => createBrowserSupabaseClient()) + if (!apolloClient) { + // Show a lightweight loading state instead of null to avoid blank screen + return ( +
+
+ +
+ ); + } - // Get the authToken - return apolloClient ? ( - // Setup the Apollo Provider + return ( //@ts-ignore - ApolloProvider react component is not typed correctly. {/* Sets up Vercel Analytics */} @@ -120,12 +142,10 @@ function AppProviderWrapper({ children }: React.PropsWithChildren<{}>) { {children} - - ) : null; + + ); } -const mutex = new AsyncMutex.Mutex(); - export function UserHandling({ children }: any) { // This will now handle all the login logic internally useRsnUser(); @@ -133,6 +153,12 @@ export function UserHandling({ children }: any) { return <>{children}; } +// Lazily loaded posthog instance - loaded after initial render to avoid blocking +let _posthogInstance: typeof PostHogType | null = null; +function getPosthog(): typeof PostHogType | null { + return _posthogInstance; +} + export function PosthogProvider({ children }: any) { useEffect(() => { const token = process.env.NEXT_PUBLIC_POSTHOG_TOKEN; @@ -144,31 +170,20 @@ export function PosthogProvider({ children }: any) { // only if on reasonote.com do we do this, not on dev.reasonote.com or localhost, or anything else. if (window.location.hostname === 'reasonote.com' || window.location.hostname === 'www.reasonote.com') { - posthog.init(token, { - api_host: '/posthog/ingest', - person_profiles: 'identified_only', - session_recording: { - maskAllInputs: false, - maskInputOptions: { - password: true, // Highly recommended as a minimum!! - // color: false, - // date: false, - // 'datetime-local': false, - // email: false, - // month: false, - // number: false, - // range: false, - // search: false, - // tel: false, - // text: false, - // time: false, - // url: false, - // week: false, - // textarea: false, - // select: false, + // Lazy-load posthog to keep it off the critical rendering path + import("posthog-js").then(({ default: posthog }) => { + _posthogInstance = posthog; + posthog.init(token, { + api_host: '/posthog/ingest', + person_profiles: 'identified_only', + session_recording: { + maskAllInputs: false, + maskInputOptions: { + password: true, + } } - } - }) + }); + }); } else { console.warn("Not on reasonote.com or www.reasonote.com, skipping Posthog init"); @@ -183,6 +198,8 @@ export function PosthogProvider({ children }: any) { const name = (rsnUser?.data?.familyName ?? "") + " " + (rsnUser?.data?.givenName ?? ""); useEffect(() => { + const posthog = getPosthog(); + if (!posthog) return; if (rsnUserId) { posthog.identify(rsnUserId, { email: email, From 93af00f5d98b88baa73fe2b944a7094771abcbe1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Mar 2026 16:30:34 +0000 Subject: [PATCH 2/3] Dynamic import reactflow on skill tree page to reduce initial bundle size https://claude.ai/code/session_01VaJKr53J9VeZTBUsuWSXxL --- .../app/app/skillsets/[skillSetId]/tree/page.page.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/next-main/app/app/skillsets/[skillSetId]/tree/page.page.tsx b/apps/next-main/app/app/skillsets/[skillSetId]/tree/page.page.tsx index ff72cb8..e46fc6a 100644 --- a/apps/next-main/app/app/skillsets/[skillSetId]/tree/page.page.tsx +++ b/apps/next-main/app/app/skillsets/[skillSetId]/tree/page.page.tsx @@ -1,5 +1,5 @@ 'use client' -import {ReactFlowProvider} from "reactflow"; +import dynamic from "next/dynamic"; import { Paper, @@ -8,6 +8,11 @@ import { import {SkillSetTree} from "../../SkillSetTree"; +const ReactFlowProvider = dynamic( + () => import("reactflow").then((mod) => ({ default: mod.ReactFlowProvider })), + { ssr: false } +); + export default function Page(){ return
From b0fa71c05b4dd120000bd341976c4babc8fcacef Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Mar 2026 17:02:49 +0000 Subject: [PATCH 3/3] Upgrade Next.js from 14.2.0 to 14.2.35 for security patches and bug fixes https://claude.ai/code/session_01VaJKr53J9VeZTBUsuWSXxL --- apps/next-main/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/next-main/package.json b/apps/next-main/package.json index b99d3bb..17b6986 100644 --- a/apps/next-main/package.json +++ b/apps/next-main/package.json @@ -96,7 +96,7 @@ "mermaid": "10.9.0", "micro": "^10.0.1", "mui-markdown": "1.1.14", - "next": "14.2.0", + "next": "14.2.35", "next-transpile-modules": "^10.0.0", "node-fetch": "^3.3.0", "node-html-markdown": "1.3.0",