From 23ccd4a50cb984621eafc71dedf858a4abfbd8a5 Mon Sep 17 00:00:00 2001 From: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:56:16 -0700 Subject: [PATCH] improvement(landing): optimize core web vitals and accessibility (#4193) * improvement(landing): optimize core web vitals and accessibility Code-split AuthModal and DemoRequestModal via next/dynamic across 7 landing components to move auth-client bundle (~150-250KB) out of the initial JS payload. Replace useSession import in navbar with direct SessionContext read to avoid pulling the entire better-auth client into the landing page bundle. Add immutable cache header for content-hashed _next/static assets. Defer PostHog session recording until user identification to avoid loading the recorder (~80KB) on anonymous visits. Fix accessibility issues flagged by Lighthouse: add missing aria-label on preview submit button, add inert to aria-hidden ReactFlow wrapper, set decorative alt on logos inside labeled links, disambiguate duplicate footer API links. Co-Authored-By: Claude Opus 4.6 (1M context) * fix(posthog): guard startSessionRecording against repeated calls on refetch The effect fires on every session reload (e.g., subscription upgrade). Calling startSessionRecording() while already recording fragments the session in the analytics dashboard. Add sessionRecordingStarted() guard so recording only starts once per page lifecycle. Co-Authored-By: Claude Opus 4.6 (1M context) * fix(config): remove redundant _next/static cache header Next.js already sets Cache-Control: public, max-age=31536000, immutable on _next/static assets natively and this cannot be overridden. The custom rule was redundant on Vercel and conflicted with the extension-based rule on self-hosted deployments due to last-match-wins ordering. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .../components/collaboration/collaboration.tsx | 6 +++++- .../(landing)/components/features/features.tsx | 6 +++++- .../(landing)/components/footer/footer-cta.tsx | 6 +++++- .../app/(landing)/components/footer/footer.tsx | 4 ++-- apps/sim/app/(landing)/components/hero/hero.tsx | 12 ++++++++++-- .../landing-preview-home.tsx | 1 + .../landing-preview-panel.tsx | 6 +++++- .../app/(landing)/components/navbar/navbar.tsx | 16 +++++++++++----- .../app/(landing)/components/pricing/pricing.tsx | 13 +++++++++++-- .../(landing)/components/templates/templates.tsx | 2 +- .../app/_shell/providers/posthog-provider.tsx | 1 + .../app/_shell/providers/session-provider.tsx | 6 ++++++ 12 files changed, 63 insertions(+), 16 deletions(-) diff --git a/apps/sim/app/(landing)/components/collaboration/collaboration.tsx b/apps/sim/app/(landing)/components/collaboration/collaboration.tsx index 13200885ba..d7b8cff120 100644 --- a/apps/sim/app/(landing)/components/collaboration/collaboration.tsx +++ b/apps/sim/app/(landing)/components/collaboration/collaboration.tsx @@ -1,12 +1,16 @@ 'use client' import { useCallback, useEffect, useRef, useState } from 'react' +import dynamic from 'next/dynamic' import Image from 'next/image' import Link from 'next/link' import { Badge } from '@/components/emcn' -import { AuthModal } from '@/app/(landing)/components/auth-modal/auth-modal' import { trackLandingCta } from '@/app/(landing)/landing-analytics' +const AuthModal = dynamic(() => + import('@/app/(landing)/components/auth-modal/auth-modal').then((m) => m.AuthModal) +) + interface DotGridProps { className?: string cols: number diff --git a/apps/sim/app/(landing)/components/features/features.tsx b/apps/sim/app/(landing)/components/features/features.tsx index 75dc371b37..f9ab0d6d6c 100644 --- a/apps/sim/app/(landing)/components/features/features.tsx +++ b/apps/sim/app/(landing)/components/features/features.tsx @@ -2,12 +2,16 @@ import { useRef, useState } from 'react' import { type MotionValue, motion, useScroll, useTransform } from 'framer-motion' +import dynamic from 'next/dynamic' import Image from 'next/image' import { Badge } from '@/components/emcn' -import { AuthModal } from '@/app/(landing)/components/auth-modal/auth-modal' import { FeaturesPreview } from '@/app/(landing)/components/features/components/features-preview' import { trackLandingCta } from '@/app/(landing)/landing-analytics' +const AuthModal = dynamic(() => + import('@/app/(landing)/components/auth-modal/auth-modal').then((m) => m.AuthModal) +) + function hexToRgba(hex: string, alpha: number): string { const r = Number.parseInt(hex.slice(1, 3), 16) const g = Number.parseInt(hex.slice(3, 5), 16) diff --git a/apps/sim/app/(landing)/components/footer/footer-cta.tsx b/apps/sim/app/(landing)/components/footer/footer-cta.tsx index 9a06d79faf..ff98ff7e15 100644 --- a/apps/sim/app/(landing)/components/footer/footer-cta.tsx +++ b/apps/sim/app/(landing)/components/footer/footer-cta.tsx @@ -2,13 +2,17 @@ import { useCallback, useRef, useState } from 'react' import { ArrowUp } from 'lucide-react' +import dynamic from 'next/dynamic' import { cn } from '@/lib/core/utils/cn' import { captureClientEvent } from '@/lib/posthog/client' -import { AuthModal } from '@/app/(landing)/components/auth-modal/auth-modal' import { useLandingSubmit } from '@/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel' import { trackLandingCta } from '@/app/(landing)/landing-analytics' import { useAnimatedPlaceholder } from '@/hooks/use-animated-placeholder' +const AuthModal = dynamic(() => + import('@/app/(landing)/components/auth-modal/auth-modal').then((m) => m.AuthModal) +) + const MAX_HEIGHT = 120 const CTA_BUTTON = diff --git a/apps/sim/app/(landing)/components/footer/footer.tsx b/apps/sim/app/(landing)/components/footer/footer.tsx index a27a1cf30b..cb4e0fb779 100644 --- a/apps/sim/app/(landing)/components/footer/footer.tsx +++ b/apps/sim/app/(landing)/components/footer/footer.tsx @@ -38,7 +38,7 @@ const BLOCK_LINKS: FooterItem[] = [ { label: 'Router', href: 'https://docs.sim.ai/blocks/router', external: true }, { label: 'Function', href: 'https://docs.sim.ai/blocks/function', external: true }, { label: 'Condition', href: 'https://docs.sim.ai/blocks/condition', external: true }, - { label: 'API', href: 'https://docs.sim.ai/blocks/api', external: true }, + { label: 'API Block', href: 'https://docs.sim.ai/blocks/api', external: true }, { label: 'Workflow', href: 'https://docs.sim.ai/blocks/workflow', external: true }, { label: 'Parallel', href: 'https://docs.sim.ai/blocks/parallel', external: true }, { label: 'Guardrails', href: 'https://docs.sim.ai/blocks/guardrails', external: true }, @@ -194,7 +194,7 @@ export default function Footer({ hideCTA }: FooterProps) { Sim + import('@/app/(landing)/components/auth-modal/auth-modal').then((m) => m.AuthModal) +) + +const DemoRequestModal = dynamic(() => + import('@/app/(landing)/components/demo-request/demo-request-modal').then( + (m) => m.DemoRequestModal + ) +) + const LandingPreview = dynamic( () => import('@/app/(landing)/components/landing-preview/landing-preview').then( diff --git a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx index aea260a3cb..338a3bd115 100644 --- a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx +++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx @@ -334,6 +334,7 @@ export const LandingPreviewHome = memo(function LandingPreviewHome({ type='button' onClick={handleSubmit} disabled={isEmpty} + aria-label='Submit message' className='flex h-[28px] w-[28px] items-center justify-center rounded-full border-0 p-0 transition-colors' style={{ background: isEmpty ? '#808080' : '#e0e0e0', diff --git a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx index 6e7ba497ae..94e39cb27b 100644 --- a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx +++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx @@ -3,13 +3,13 @@ import { memo, useCallback, useEffect, useRef, useState } from 'react' import { AnimatePresence, motion } from 'framer-motion' import { ArrowUp } from 'lucide-react' +import dynamic from 'next/dynamic' import { useRouter } from 'next/navigation' import { createPortal } from 'react-dom' import { Blimp, BubbleChatPreview, ChevronDown, MoreHorizontal, Play } from '@/components/emcn' import { AgentIcon, HubspotIcon, OpenAIIcon, SalesforceIcon } from '@/components/icons' import { LandingPromptStorage } from '@/lib/core/utils/browser-storage' import { captureClientEvent } from '@/lib/posthog/client' -import { AuthModal } from '@/app/(landing)/components/auth-modal/auth-modal' import { EASE_OUT, type EditorPromptData, @@ -21,6 +21,10 @@ import { } from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data' import { trackLandingCta } from '@/app/(landing)/landing-analytics' +const AuthModal = dynamic(() => + import('@/app/(landing)/components/auth-modal/auth-modal').then((m) => m.AuthModal) +) + type PanelTab = 'copilot' | 'editor' const EDITOR_BLOCK_ICONS: Record> = { diff --git a/apps/sim/app/(landing)/components/navbar/navbar.tsx b/apps/sim/app/(landing)/components/navbar/navbar.tsx index 54579bc173..da1e114f07 100644 --- a/apps/sim/app/(landing)/components/navbar/navbar.tsx +++ b/apps/sim/app/(landing)/components/navbar/navbar.tsx @@ -1,13 +1,13 @@ 'use client' -import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react' +import { useCallback, useContext, useEffect, useRef, useState, useSyncExternalStore } from 'react' +import dynamic from 'next/dynamic' import Image from 'next/image' import Link from 'next/link' import { useSearchParams } from 'next/navigation' import { GithubOutlineIcon } from '@/components/icons' -import { useSession } from '@/lib/auth/auth-client' import { cn } from '@/lib/core/utils/cn' -import { AuthModal } from '@/app/(landing)/components/auth-modal/auth-modal' +import { SessionContext } from '@/app/_shell/providers/session-provider' import { BlogDropdown, type NavBlogPost, @@ -17,6 +17,10 @@ import { GitHubStars } from '@/app/(landing)/components/navbar/components/github import { trackLandingCta } from '@/app/(landing)/landing-analytics' import { getBrandConfig } from '@/ee/whitelabeling' +const AuthModal = dynamic(() => + import('@/app/(landing)/components/auth-modal/auth-modal').then((m) => m.AuthModal) +) + type DropdownId = 'docs' | 'blog' | null interface NavLink { @@ -48,7 +52,9 @@ interface NavbarProps { export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps) { const brand = getBrandConfig() const searchParams = useSearchParams() - const { data: session, isPending: isSessionPending } = useSession() + const sessionCtx = useContext(SessionContext) + const session = sessionCtx?.data ?? null + const isSessionPending = sessionCtx?.isPending ?? true const isAuthenticated = Boolean(session?.user?.id) const isBrowsingHome = searchParams.has('home') const useHomeLinks = isAuthenticated || isBrowsingHome @@ -125,7 +131,7 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps ) : ( Sim + import('@/app/(landing)/components/auth-modal/auth-modal').then((m) => m.AuthModal) +) + +const DemoRequestModal = dynamic(() => + import('@/app/(landing)/components/demo-request/demo-request-modal').then( + (m) => m.DemoRequestModal + ) +) + interface PricingTier { id: string name: string diff --git a/apps/sim/app/(landing)/components/templates/templates.tsx b/apps/sim/app/(landing)/components/templates/templates.tsx index cb799237ae..cbe2d0609b 100644 --- a/apps/sim/app/(landing)/components/templates/templates.tsx +++ b/apps/sim/app/(landing)/components/templates/templates.tsx @@ -470,7 +470,7 @@ export default function Templates() { aria-labelledby={`template-tab-${activeIndex}`} className='relative hidden flex-1 lg:block' > -