Skip to content

improvement(landing): optimize core web vitals and accessibility#4193

Merged
emir-karabeg merged 3 commits intostagingfrom
improvement/core-web-vitals
Apr 16, 2026
Merged

improvement(landing): optimize core web vitals and accessibility#4193
emir-karabeg merged 3 commits intostagingfrom
improvement/core-web-vitals

Conversation

@emir-karabeg
Copy link
Copy Markdown
Collaborator

Summary

  • Code-split AuthModal and DemoRequestModal via next/dynamic across 7 landing components to move the auth-client bundle 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-Control header for content-hashed _next/static assets
  • Defer PostHog session recording until user identification to skip loading the recorder on anonymous visits
  • Fix accessibility issues flagged by Lighthouse: missing aria-label, inert on aria-hidden wrapper, decorative alt on logos, duplicate link labels

Context

PageSpeed Insights (Apr 15, 2026) shows sim.ai failing Core Web Vitals — LCP 3.4s (mobile) / 2.6s (desktop), Performance score 75 (mobile) / 82 (desktop). Root causes: ~675KB unused JS on landing page, 3.0s JS execution time, poor cache lifetimes for hashed static assets, and PostHog recorder loading on anonymous visits. Accessibility score was 88 (desktop) / 92 (mobile) due to missing labels and focusable elements inside aria-hidden containers.

Changes

Code-split auth modals (hero.tsx, navbar.tsx, features.tsx, collaboration.tsx, pricing.tsx, footer-cta.tsx, landing-preview-panel.tsx)

  • Replace static import { AuthModal } / import { DemoRequestModal } with dynamic() (SSR preserved, client-side code-split)
  • Moves auth-client bundle (better-auth + SSO/Stripe/org plugins) into a separate chunk loaded on interaction

Navbar session optimization (navbar.tsx)

  • Replace import { useSession } from '@/lib/auth/auth-client' with useContext(SessionContext) from session-provider
  • useSession in auth-client.ts is a thin wrapper around the same context, but importing it forces bundling the entire createAuthClient() call with all plugins
  • Direct context read is semantically identical, avoids the bundle cost

Cache headers (next.config.ts)

  • Add Cache-Control: public, max-age=31536000, immutable for /_next/static/:path*
  • These assets have content hashes in filenames — URL changes on deploy, safe to cache indefinitely
  • Matches Vercel's default behavior, ensures parity for self-hosted (Docker) deployments

PostHog session recording deferral (posthog-provider.tsx, session-provider.tsx)

  • Add disable_session_recording: true to PostHog init config — prevents recorder JS (~80KB) from loading on anonymous visits
  • Call posthog.startSessionRecording() after posthog.identify() for authenticated users
  • Called without overrides to respect dashboard-level sampling and linked flag config

Accessibility fixes (footer.tsx, navbar.tsx, landing-preview-home.tsx, templates.tsx)

  • Set alt='' on logo images inside links that already have aria-label and sr-only text (image is decorative)
  • Add aria-label='Submit message' to icon-only button in landing preview
  • Add inert attribute alongside aria-hidden='true' on ReactFlow preview wrapper to prevent focusable descendants from tab order
  • Rename duplicate "API" footer link to "API Block" to disambiguate from "API" in Product column

Type of Change

  • Improvement
  • Bug fix

Testing

  • Pre-commit hooks (biome lint/format) pass
  • All modal triggers render in SSR (no ssr: false — buttons visible in initial HTML)
  • Auth state in navbar reads from same SessionContext provided by root layout SessionProvider
  • PostHog disable_session_recording and startSessionRecording() verified against posthog-js 1.364.4 type definitions
  • _next/static cache rule only applies to content-hashed assets — safe for immutable caching
  • No styling changes — all diffs are import swaps, config additions, or HTML attribute additions

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

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>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Apr 16, 2026 2:34am

Request Review

@cursor
Copy link
Copy Markdown

cursor bot commented Apr 16, 2026

PR Summary

Medium Risk
Moderate risk due to changes in auth/session wiring and PostHog recording behavior; bundle-splitting and a11y tweaks are otherwise low impact.

Overview
Improves landing-page performance by code-splitting AuthModal (and DemoRequestModal where used) via next/dynamic across multiple landing components, reducing initial JS and deferring auth/demo code until interaction.

Updates the landing navbar to read auth state directly from SessionContext (instead of importing the auth client hook) to avoid pulling additional auth-client code into the landing bundle.

Tightens analytics/a11y behavior: disables PostHog session recording at init and starts recording only after user identification, adds missing aria-label for icon-only submit buttons, applies inert to aria-hidden template previews, makes logo images decorative with alt='', and renames the footer “API” block link to “API Block`.”

Reviewed by Cursor Bugbot for commit 9bc82bf. Configure here.

@emir-karabeg
Copy link
Copy Markdown
Collaborator Author

@cursor review

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 16, 2026

Greptile Summary

This PR reduces initial JS payload on the landing page by code-splitting AuthModal and DemoRequestModal via next/dynamic, replaces useSession (which drags in the full auth-client bundle) with a direct useContext(SessionContext) read in the navbar, defers PostHog session recording until after user identification, and fixes several Lighthouse accessibility findings. The implementation is correct and consistent across all 7 affected components.

  • The PR description states that a Cache-Control: public, max-age=31536000, immutable header was added for /_next/static/:path* in next.config.ts, but next.config.ts is not in the changeset and the file contains no such rule — this optimization is missing for self-hosted (Docker) deployments, though Vercel handles it automatically.

Confidence Score: 5/5

Safe to merge — all code changes are correct; one stated optimization (static asset cache headers) is missing from the diff but doesn't affect correctness.

All four optimizations are structurally sound: dynamic imports use the correct named-export pattern with SSR enabled, the SessionContext swap uses safe fallback values, PostHog deferral is guarded against duplicate recording calls, and accessibility fixes are correct. The only finding is a P2 gap between the PR description and the actual changeset (missing _next/static cache header in next.config.ts), which doesn't break any user-facing behavior on Vercel but leaves self-hosted deployments without the claimed caching benefit.

No files require special attention; the missing change is in next.config.ts which is outside the current changeset.

Important Files Changed

Filename Overview
apps/sim/app/_shell/providers/session-provider.tsx Adds startSessionRecording() after posthog.identify() with a sessionRecordingStarted() guard to prevent duplicate recordings on refetch; effect deps and guard logic are correct.
apps/sim/app/_shell/providers/posthog-provider.tsx Adds disable_session_recording: true to PostHog init; session_recording config block is preserved and will apply when recording is manually started for authenticated users.
apps/sim/app/(landing)/components/navbar/navbar.tsx Replaces useSession (pulls full auth-client) with useContext(SessionContext) using safe fallbacks; AuthModal code-split via dynamic(); logo alt cleared to empty string since link already carries aria-label.
apps/sim/app/(landing)/components/hero/hero.tsx Both AuthModal and DemoRequestModal correctly converted to dynamic() with named-export .then(m => m.X) pattern; SSR remains enabled (no ssr: false).
apps/sim/app/(landing)/components/templates/templates.tsx Adds inert alongside aria-hidden='true' on the ReactFlow preview wrapper to remove focusable descendants from tab order — correct accessibility fix.
apps/sim/app/(landing)/components/footer/footer.tsx Logo alt cleared to empty string (decorative inside an aria-label-bearing link); "API" block link renamed to "API Block" to disambiguate from Product column.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant Next.js SSR
    participant SessionProvider
    participant PostHogProvider
    participant PostHog

    Browser->>Next.js SSR: Request landing page
    Next.js SSR-->>Browser: HTML (navbar buttons, hero CTAs rendered)
    Note over Browser: AuthModal/DemoRequestModal JS NOT in initial bundle

    Browser->>PostHogProvider: useEffect → posthog.init(disable_session_recording: true)
    Browser->>SessionProvider: useEffect → loadSession()
    SessionProvider-->>Browser: session data returned

    alt User is authenticated
        SessionProvider->>PostHog: posthog.identify(user.id, ...)
        SessionProvider->>PostHog: sessionRecordingStarted()? → false
        SessionProvider->>PostHog: startSessionRecording()
    else Anonymous visitor
        SessionProvider->>PostHog: posthog.reset()
        Note over PostHog: Recording stays disabled (~80KB saved)
    end

    Browser->>Browser: User clicks CTA button
    Browser->>Next.js: dynamic import → AuthModal chunk loaded
    Browser-->>Browser: Modal opens
Loading

Reviews (2): Last reviewed commit: "fix(config): remove redundant _next/stat..." | Re-trigger Greptile

Comment thread apps/sim/app/_shell/providers/session-provider.tsx Outdated
…efetch

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>
Comment thread apps/sim/next.config.ts Outdated
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>
@emir-karabeg
Copy link
Copy Markdown
Collaborator Author

@cursor review

@emir-karabeg
Copy link
Copy Markdown
Collaborator Author

@greptile

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 9bc82bf. Configure here.

@emir-karabeg emir-karabeg merged commit 23ccd4a into staging Apr 16, 2026
14 checks passed
@emir-karabeg emir-karabeg deleted the improvement/core-web-vitals branch April 16, 2026 02:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant