Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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) <noreply@anthropic.com>

* 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) <noreply@anthropic.com>

* 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) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
  • Loading branch information
emir-karabeg and claude authored Apr 16, 2026
commit 23ccd4a50cb984621eafc71dedf858a4abfbd8a5
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 5 additions & 1 deletion apps/sim/app/(landing)/components/features/features.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion apps/sim/app/(landing)/components/footer/footer-cta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(landing)/components/footer/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -194,7 +194,7 @@ export default function Footer({ hideCTA }: FooterProps) {
<Link href='/' aria-label='Sim home'>
<Image
src='/logo/sim-landing.svg'
alt='Sim'
alt=''
width={85}
height={26}
className='h-[26.4px] w-auto'
Expand Down
12 changes: 10 additions & 2 deletions apps/sim/app/(landing)/components/hero/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@

import dynamic from 'next/dynamic'
import { cn } from '@/lib/core/utils/cn'
import { AuthModal } from '@/app/(landing)/components/auth-modal/auth-modal'
import { DemoRequestModal } from '@/app/(landing)/components/demo-request/demo-request-modal'
import { trackLandingCta } from '@/app/(landing)/landing-analytics'

const AuthModal = dynamic(() =>
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<string, React.ComponentType<{ className?: string }>> = {
Expand Down
16 changes: 11 additions & 5 deletions apps/sim/app/(landing)/components/navbar/navbar.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -125,7 +131,7 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
) : (
<Image
src='/logo/sim-landing.svg'
alt='Sim'
alt=''
width={71}
height={22}
className='h-[22px] w-auto'
Expand Down
13 changes: 11 additions & 2 deletions apps/sim/app/(landing)/components/pricing/pricing.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
'use client'

import dynamic from 'next/dynamic'
import { Badge } from '@/components/emcn'
import { AuthModal } from '@/app/(landing)/components/auth-modal/auth-modal'
import { DemoRequestModal } from '@/app/(landing)/components/demo-request/demo-request-modal'
import { trackLandingCta } from '@/app/(landing)/landing-analytics'

const AuthModal = dynamic(() =>
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
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/(landing)/components/templates/templates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ export default function Templates() {
aria-labelledby={`template-tab-${activeIndex}`}
className='relative hidden flex-1 lg:block'
>
<div aria-hidden='true' className='h-full'>
<div aria-hidden='true' inert className='h-full'>
<LandingPreviewWorkflow
key={activeIndex}
workflow={activeWorkflow}
Expand Down
1 change: 1 addition & 0 deletions apps/sim/app/_shell/providers/posthog-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
capture_performance: false,
capture_dead_clicks: false,
enable_heatmaps: false,
disable_session_recording: true,
session_recording: {
maskAllInputs: false,
maskInputOptions: {
Expand Down
6 changes: 6 additions & 0 deletions apps/sim/app/_shell/providers/session-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
email_verified: data.user.emailVerified,
created_at: data.user.createdAt,
})
if (
typeof posthog.startSessionRecording === 'function' &&
!posthog.sessionRecordingStarted()
) {
posthog.startSessionRecording()
Comment thread
waleedlatif1 marked this conversation as resolved.
}
} else {
posthog.reset()
}
Expand Down
Loading