Skip to content

fix(docs): fix Core Web Vitals regressions on docs.sim.ai#5630

Open
waleedlatif1 wants to merge 8 commits into
stagingfrom
docs-perf-fixes
Open

fix(docs): fix Core Web Vitals regressions on docs.sim.ai#5630
waleedlatif1 wants to merge 8 commits into
stagingfrom
docs-perf-fixes

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Fixed the docs sidebar prefetching every page in the doc tree on initial load (dozens of concurrent RSC requests) - wired fumadocs' sidebar.prefetch option through the custom sidebar components, which were bypassing it via plain next/link
  • Fixed the <Video> component eagerly downloading full video files on mount (autoPlay forces a full fetch regardless of preload) - gated loading behind an IntersectionObserver, fixes every doc page that embeds one
  • Fixed /icon.svg 404ing in production - the i18n middleware matcher excluded favicon/robots.txt/etc but not this file
  • Enabled productionBrowserSourceMaps (repo is already public, zero exposure, real debuggability win)
  • Bumped shiki 4.0.0 → 4.3.1 and fumadocs-core/fumadocs-ui/fumadocs-mdx to their latest compatible versions (verified via build + visual smoke test). Attempted the fumadocs-openapi major bump too but reverted it - v11's page factory became client-only, a real breaking change beyond its declared peer deps that needs its own migration PR, not a rushed dependency bump

Type of Change

  • Bug fix
  • Performance improvement

Testing

Measured empirically under real trace-based (devtools) CPU/network throttling against the live site and a clean local production build:

  • Performance: 59 → 71 (mobile, devtools throttling)
  • RSC prefetch requests on page load: 63 → 11
  • Video requests/bytes on page load: 3 requests / 5MB → 0
  • Verified full production build succeeds cleanly (3974 pages, including API reference)
  • Verified Shiki syntax highlighting still renders correctly after the bump

Found and documented (not fixed, pre-existing, unrelated to this diff): a React hydration warning (#418) present on live production before any of these changes.

Checklist

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

Empirically measured under real trace-based (devtools) CPU/network
throttling against the live site: mobile Performance 59, LCP 9.2s
(TTFB 745ms + 8.4s element render delay).

- sidebar-components.tsx / [lang]/layout.tsx: the docs sidebar renders
  every page in the doc tree as a link at once. Next's default
  viewport-prefetch fired an RSC payload fetch for every one of them on
  initial load - dozens of concurrent requests competing with the page's
  own content for bandwidth. Wired fumadocs' documented `sidebar.prefetch`
  option through to the custom SidebarItem/SidebarFolder components (which
  were bypassing it entirely, using next/link directly with no prefetch
  prop) via the `useSidebar()` context hook.
- video.tsx: `autoPlay` forces browsers to fetch the full video file
  immediately on mount regardless of `preload`. Gated actual src loading
  behind an IntersectionObserver so a page with several of these doesn't
  pull down every video up front (5MB across 3 requests, in this case).
  Single shared component - fixes every doc page that embeds one.
- proxy.ts: the i18n middleware matcher excluded favicon/robots.txt/etc
  but not `icon.svg`, so every request for it got routed through i18n
  negotiation instead of served as a static file, 404ing in production.
- next.config.ts: enable productionBrowserSourceMaps - safe since this
  repo's source is already fully public, real debuggability benefit,
  zero performance cost.
- shiki 4.0.0 -> 4.3.1 (verified: syntax highlighting still renders
  correctly). Attempted a coordinated fumadocs-core/ui/mdx/openapi
  upgrade to latest; fumadocs-openapi's v11 factory function became
  client-only (breaking change beyond its declared peer deps, requiring
  a component-boundary restructure), so only the safe, verified,
  docs-exclusive bumps (fumadocs-core/ui/mdx, shiki) are included here -
  the openapi major bump needs its own dedicated migration PR.

Verified via a real production build (dummy env, all 3974 pages
including API reference render/build cleanly) and a clean (non-stale)
local server: Performance 59 -> 71 measured under real devtools
throttling, RSC prefetch requests 63 -> 11, video requests/bytes 3/5MB
-> 0. A pre-existing React hydration warning (#418) was found and
confirmed present on live production before any of these changes,
unrelated to this diff - documented, not blocking.
@waleedlatif1 waleedlatif1 requested a review from a team as a code owner July 12, 2026 23:01
@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 13, 2026 1:00am

Request Review

@cursor

cursor Bot commented Jul 12, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Docs-site performance, SEO, and UI polish only; no auth, data, or API behavior changes.

Overview
Targets docs.sim.ai load performance by stopping the sidebar from prefetching the whole doc tree (sidebar.prefetch: false plus wiring prefetch from useSidebar() into custom sidebar links), lazy-loading embedded videos until they are near the viewport (IntersectionObserver, preload="none", deferred src), and code-splitting Ask Sim so the chat UI loads only after the user opens it.

Also fixes /icon.svg being rewritten by i18n middleware, enables productionBrowserSourceMaps, and bumps shiki to 4.3.1. Hreflang/canonical URLs now use stripLocalePrefix so locale segments are not stripped from unrelated path substrings (e.g. enterprise, enrich).

Smaller follow-ups: navbar aria-current, heading link focus styles, lightbox/workflow modal focus management, API response status listbox semantics, structured-data copy (Chat vs Mothership) and home URL matching with a trailing slash.

Reviewed by Cursor Bugbot for commit 16145dc. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 fef9274. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves docs performance and fixes a few docs rendering details. The main changes are:

  • Disabled broad sidebar prefetching through the custom sidebar links.
  • Lazy-loaded docs videos until they enter the viewport, with a fallback for unsupported observers.
  • Split the Ask AI panel into a deferred client chunk.
  • Fixed locale-safe canonical and hreflang URL generation.
  • Excluded /icon.svg from the docs proxy matcher.
  • Enabled production browser source maps and updated Shiki-related lockfile entries.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • The video fallback now covers the previously broken no-observer runtime path.
  • The other changes are contained to docs rendering, metadata, middleware matching, and dependency updates.

Important Files Changed

Filename Overview
apps/docs/components/ui/video.tsx Adds viewport-gated video loading with an eager-load fallback when observer support is unavailable.
apps/docs/components/docs-layout/sidebar-components.tsx Passes the fumadocs sidebar prefetch setting into custom sidebar links.
apps/docs/components/ai/ask-ai.tsx Defers the Ask AI panel behind a client-only dynamic import.
apps/docs/components/ai/ask-ai-panel.tsx Moves the Ask AI chat panel UI and behavior into a dedicated client component.
apps/docs/app/[lang]/[[...slug]]/page.tsx Builds canonical and language alternate URLs with path-prefix-aware locale stripping.
apps/docs/proxy.ts Excludes the production icon asset from locale proxy handling.

Reviews (6): Last reviewed commit: "fix(docs): fill the Ask Sim UI gap while..." | Re-trigger Greptile

Comment thread apps/docs/components/ui/video.tsx
@greptile-apps

greptile-apps Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR reduces docs page load work and updates a few docs runtime settings. The main changes are:

  • Sidebar links now respect the fumadocs prefetch setting.
  • Docs videos defer loading until near the viewport.
  • /icon.svg is excluded from the i18n proxy matcher.
  • Production browser source maps are enabled.
  • The docs Shiki resolution is updated.

Confidence Score: 4/5

The video lazy-loading path needs a small runtime fallback before merging.

  • Sidebar prefetch wiring matches the surrounding fumadocs and Next link contracts.
  • The icon matcher, source map setting, and Shiki update look consistent with the changed docs app paths.
  • The changed video component can leave inline videos without a source when IntersectionObserver is unavailable.

apps/docs/components/ui/video.tsx

Important Files Changed

Filename Overview
apps/docs/app/[lang]/layout.tsx Adds sidebar.prefetch: false so the docs layout can suppress sidebar link prefetching.
apps/docs/components/docs-layout/sidebar-components.tsx Reads the fumadocs sidebar prefetch setting and passes it to the custom Next links.
apps/docs/components/ui/video.tsx Defers video source attachment until intersection, but needs a fallback when the observer API is missing.
apps/docs/next.config.ts Enables production browser source maps for the docs app.
apps/docs/package.json Updates the docs app Shiki dependency.
apps/docs/proxy.ts Excludes /icon.svg from the docs i18n proxy matcher.
bun.lock Updates the locked docs dependency graph for the Shiki change and related resolutions.

Reviews (2): Last reviewed commit: "fix(docs): fix Core Web Vitals regressio..." | Re-trigger Greptile

Comment thread apps/docs/components/ui/video.tsx
The lazy-load gate from the previous commit threw before isInView could
ever become true in environments lacking IntersectionObserver (older
browsers, some embedded webviews), leaving videos permanently
source-less instead of falling back to eager loading.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 9713556. Configure here.

Repo convention is TSDoc-only, no plain // comments.
Audited with parallel subagents against the accessibility and SEO skill
checklists, each fix verified by reading the actual code (not assumed):

Accessibility:
- lightbox.tsx: focus was never captured/restored on close, and Tab
  escaped the modal to the page behind it (no focus trap on the single
  focusable element)
- heading.tsx: the per-heading copy-link icon only appeared on hover,
  invisible to keyboard-only navigation (added peer-focus-visible)
- navbar.tsx: active nav tab had no aria-current
- response-section.tsx: the status-code dropdown had no
  aria-haspopup/aria-expanded/role, and no Escape-to-close
- workflow-preview.tsx: same focus-trap gap as lightbox.tsx on the
  expanded-canvas modal

SEO:
- page.tsx: generateMetadata's hreflang/canonical URLs used a naive
  String.replace to strip the locale prefix, which also matched "/en"
  inside unrelated slugs (platform/enterprise, integrations/enrich,
  platform/self-hosting/environment-variables), corrupting those pages'
  canonical and alternate-language URLs. Replaced with a prefix-only strip.
- structured-data.tsx: the SoftwareApplication JSON-LD block compared
  url === baseUrl (no trailing slash) against the homepage's actual url
  (always has a trailing slash), so the condition was always false and
  this structured data never rendered anywhere, including the homepage.
- structured-data.tsx: "Mothership" in the indexed featureList violated
  the constitution's required language (the agent is "Sim", the surface
  is "Chat") - this ships in JSON-LD search engines parse.
The chat panel (useChat from @ai-sdk/react, Streamdown + its CSS) was
mounted unconditionally in the root layout on every single page, so
its full weight loaded and executed even though the widget starts
closed on every page view.

Traced via the LCP breakdown insight under real devtools CPU/network
throttling: the LCP text element (the intro paragraph) had a ~8s
element render delay despite a ~13ms TTFB, and bootup-time attributed
~4.3s of scripting time to a single chunk containing React/ReactDOM's
own runtime plus this widget's eagerly-bundled dependencies.

Split into a lightweight ask-ai.tsx (just the toggle button + open
state) and ask-ai-panel.tsx (the actual chat UI, useChat, Streamdown),
loaded via next/dynamic(..., { ssr: false }) only when the user opens
the widget. Verified: the panel's chunk now has zero network requests
on initial page load.

Measured (mobile, devtools throttling, /introduction):
- Performance: 69 -> 75
- LCP: 8.0s -> 6.4s
- TBT: 260ms -> 130ms

The remaining ~6.4s LCP delay traces to the same shared chunk, now
identified as core React/ReactDOM hydration cost for this page's
sidebar/TOC/breadcrumb tree rather than an isolated bug - a real,
larger initiative (hydration architecture, not a surgical fix),
documented here rather than rushed.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/docs/components/ai/ask-ai.tsx Outdated
The panel split unmounted AskAIPanel entirely on close, discarding
useChat's message state - reopening always started an empty
conversation, unlike the original single-component layout where
useChat lived in a component that never unmounted.

Fixed by keeping the panel mounted (via a hasOpened flag that never
resets) once first opened, and having the panel itself return null
when closed rather than being conditionally removed from the tree by
its parent - hooks still run every render, so useChat's state persists
across visibility toggles. The dynamic import still only fires on the
first open, so the initial-load win is unchanged.

Verified via a real click-through (open, type, close, reopen): input
persists correctly, and the panel chunk still has zero network
requests on initial page load. Performance unchanged at 75.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/docs/components/ai/ask-ai.tsx
handleOpen set open=true synchronously, hiding the trigger button
before the dynamically imported panel had a chance to render anything
(next/dynamic renders null by default with no loading option) - on a
slow connection neither the button nor the panel was visible.

Added a loading fallback in the same fixed position so there's no gap
between the button disappearing and the real panel appearing.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 16145dc. Configure here.

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