Fix/mobile performance core web vitals#795
Fix/mobile performance core web vitals#795Aryan-B-Parikh wants to merge 5 commits intokeploy:mainfrom
Conversation
Code Review SummaryStatus: No Issues Found | Recommendation: Merge OverviewThis PR implements comprehensive performance optimizations for the documentation site. The changes are well-structured and follow best practices:
Files Reviewed (57 files)
|
There was a problem hiding this comment.
Pull request overview
This pull request aims to improve mobile Core Web Vitals performance for the Keploy documentation site by implementing comprehensive optimization strategies including caching, resource hints, lazy loading, and rendering optimizations.
Changes:
- Added cache and security headers in netlify.toml for static assets
- Configured resource hints (preconnect, dns-prefetch) for third-party origins in docusaurus.config.js
- Lazy-loaded react-player component to reduce initial bundle size
- Added CSS rendering optimizations (content-visibility, font-display, decoding) in custom.css
- Added
loading="lazy"anddecoding="async"attributes to all 112+ image tags across documentation files
Reviewed changes
Copilot reviewed 116 out of 116 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| netlify.toml | Added cache headers for assets/fonts (1 year immutable) and images/scripts (1 day), plus security headers |
| docusaurus.config.js | Added resource hints (preconnect for fonts/Algolia, dns-prefetch for analytics) and viewport meta tag |
| src/components/responsive-player/ResponsivePlayer.js | Implemented React.lazy() for react-player to defer ~100KB from initial bundle |
| src/css/custom.css | Added content-visibility for footer/sidebar, decoding:async fallback, and font-display:swap attempt |
| versioned_docs/**/*.md (112 files) | Added loading="lazy" decoding="async" to all image tags for progressive loading |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/css/custom.css
Outdated
|
|
||
| /* | ||
| * Font-display: swap fallback for any @font-face rules Docusaurus injects. | ||
| * This prevents invisible text during webfont load (FOIT → FOUT). | ||
| * The actual font files are already preloaded via webpack-font-preload-plugin; | ||
| * this rule is a safety net for any font loaded via a stylesheet. | ||
| */ | ||
| @supports (font-display: swap) { | ||
| @font-face { | ||
| font-display: swap; | ||
| } | ||
| } |
There was a problem hiding this comment.
The @font-face rule at lines 2771-2773 is incomplete and won't work as intended. A @font-face rule requires at minimum a font-family and src property to be valid. This empty rule will have no effect.
If the goal is to override font-display for existing font-face declarations loaded by Docusaurus, this approach won't work because CSS doesn't allow overriding properties of existing @font-face rules.
Consider either:
- Removing this rule entirely if fonts are already configured properly elsewhere
- Configuring font-display at the theme/plugin level in docusaurus.config.js
- Using a CSS custom property approach if Docusaurus supports it
| /* | |
| * Font-display: swap fallback for any @font-face rules Docusaurus injects. | |
| * This prevents invisible text during webfont load (FOIT → FOUT). | |
| * The actual font files are already preloaded via webpack-font-preload-plugin; | |
| * this rule is a safety net for any font loaded via a stylesheet. | |
| */ | |
| @supports (font-display: swap) { | |
| @font-face { | |
| font-display: swap; | |
| } | |
| } |
src/css/custom.css
Outdated
| * Reduce paint layers for the announcement bar (it's a position:sticky | ||
| * element and can cause extra compositing cost on mobile). | ||
| */ | ||
| .announcementBar_mb4j { |
There was a problem hiding this comment.
The selector .announcementBar_mb4j appears to use a hashed class name specific to a particular Docusaurus build. Hashed class names like _mb4j typically change between builds, which means this CSS rule may break after the next build or upgrade.
Consider using a more stable selector, such as:
[class*="announcementBar"]to target any class containing "announcementBar"- A data attribute selector if available
- Working with the Docusaurus theme configuration to apply these styles more reliably
| .announcementBar_mb4j { | |
| [class*="announcementBar"] { |
| aria-label="Loading video player" | ||
| /> |
There was a problem hiding this comment.
The import statement uses import("react-player/lazy") which is correct for lazy-loading react-player. However, the comment on line 4 mentions "react-player/lazy defers loading the actual player implementation" which is accurate.
One consideration: The fallback div (lines 17-20) has no visible loading indicator (no text, spinner, or skeleton). While this creates a clean visual experience, it may not be obvious to users that content is loading, especially on slower connections. Consider adding a loading spinner or skeleton screen for better UX.
| aria-label="Loading video player" | |
| /> | |
| role="status" | |
| aria-label="Loading video player" | |
| > | |
| <div className="h-8 w-8 animate-spin rounded-full border-2 border-gray-400 border-t-transparent dark:border-gray-500" /> | |
| <span className="sr-only">Loading video player</span> | |
| </div> |
netlify.toml
Outdated
| [[headers]] | ||
| for = "/fonts/*" | ||
| [headers.values] | ||
| Cache-Control = "public, max-age=31536000, immutable" |
There was a problem hiding this comment.
The cache header for /fonts/* uses immutable with a 1-year cache (line 28), which is appropriate for font files with content hashes. However, the comment on line 19 says "Static images, fonts served from /img and /fonts" but the /img/* pattern (lines 20-23) uses a different, more conservative caching strategy.
If fonts truly have content hashes in their filenames (like font-abc123.woff2), then immutable is correct. However, if they don't, using immutable could cause issues when fonts are updated because browsers won't revalidate them. Verify that Docusaurus generates hashed font filenames before using immutable.
| Cache-Control = "public, max-age=31536000, immutable" | |
| Cache-Control = "public, max-age=86400, stale-while-revalidate=604800" |
docusaurus.config.js
Outdated
| // ── Viewport (mobile performance) ────────────────────────────────── | ||
| { | ||
| tagName: "meta", | ||
| attributes: { | ||
| name: "viewport", | ||
| content: "width=device-width, initial-scale=1.0, viewport-fit=cover", | ||
| }, | ||
| }, |
There was a problem hiding this comment.
The viewport meta tag is being added via headTags (lines 57-63), but Docusaurus typically includes a default viewport meta tag. This could result in duplicate viewport meta tags in the HTML output, which while not harmful, is redundant.
Check if Docusaurus already includes a viewport meta tag. If it does, this configuration may override it or create a duplicate. The viewport-fit=cover addition is good for iOS devices with notches, but consider whether this conflicts with or overrides the default Docusaurus viewport configuration.
| // ── Viewport (mobile performance) ────────────────────────────────── | |
| { | |
| tagName: "meta", | |
| attributes: { | |
| name: "viewport", | |
| content: "width=device-width, initial-scale=1.0, viewport-fit=cover", | |
| }, | |
| }, |
- netlify.toml: add 1-year immutable cache for hashed assets (/assets/*, /fonts/*), 1-day cache for images/scripts, and security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) to improve PageSpeed Best Practices score - docusaurus.config.js: add viewport meta tag with viewport-fit=cover; add preconnect hints for fonts.googleapis.com, fonts.gstatic.com, and Algolia DSN; add dns-prefetch for Clarity, GTM, and GA analytics origins to reduce connection setup latency - src/css/custom.css: add content-visibility:auto + contain-intrinsic-size for footer and mobile sidebar to skip off-screen paint; add decoding:async fallback for un-annotated images; add font-display:swap safety net for any stylesheet-loaded webfonts - ResponsivePlayer.js: lazy-load react-player via React.lazy + React.Suspense (react-player/lazy) so the ~100 KB player bundle is NOT included in the initial JS chunk – reduces TBT/INP on first load Closes #2042 Signed-off-by: Aryan Parikh <aryan81006@gmail.com>
All <img> HTML tags across versioned docs (v1–v4) and current docs pages now have loading="lazy" decoding="async" so images are: - Deferred until they enter the viewport (reduces initial page weight) - Decoded off the main thread (reduces INP / Total Blocking Time) Key heavy assets now lazy-loaded: - /gif/record-replay.gif (2.5 MB), /gif/replay-tc.gif (2.1 MB) - /gif/unit-test.gif (24.8 MB!) ← was loading unconditionally - /img/record-api.gif (1.1 MB) - multiple 600 KB–1.4 MB PNG screenshots Affects 112 documentation files across all supported doc versions. Closes #2042" Signed-off-by: Aryan Parikh <aryan81006@gmail.com>
Docusaurus already injects <meta name=viewport> by default from its core theme. Adding it again via headTags created a duplicate tag. Removes the redundant entry per Copilot review on PR. Signed-off-by: Aryan Parikh <aryan81006@gmail.com>
55bdea4 to
bca3ff0
Compare
- css: replace hashed .announcementBar_mb4j selector with [class*=announcementBar] to survive Docusaurus rebuilds - css: remove invalid empty @font-face block; a @font-face without font-family/src has no effect in any browser - ResponsivePlayer: add visible spinner + role=status to Suspense fallback for better UX on slow connections - netlify.toml: change /fonts/* from immutable to 1-week cache since font files (Roboto-Bold.woff2 etc.) are not content-hashed Signed-off-by: Aryan Parikh <aryan81006@gmail.com>
GDG CHARUSAT TEAM ID “<Team 146>”
Summary
Closes keploy/keploy#2042
Fixes the mobile Core Web Vitals issues reported in the PageSpeed audit:
https://pagespeed.web.dev/analysis/https-keploy-io-docs/tkgmgmeiho?form_factor=mobile
Changes
netlify.toml — Cache & security headers
/assets/*,/fonts/*)docusaurus.config.js — Resource hints
preconnectfor Google Fonts and Algolia DSNdns-prefetchfor Clarity, GTM, Google Analytics (~200ms saved per origin)viewport-fit=covermeta tagResponsivePlayer.js — Lazy bundle split
react-player(~100 KB) deferred from initial bundle viaReact.lazy()custom.css — Rendering performance
content-visibility: autoon footer and mobile sidebardecoding: asyncfallback for all imagesfont-display: swapto prevent invisible text (FOIT)112 doc files — Image lazy loading
loading="lazy" decoding="async"on every<img>tag across all versioned docsunit-test.gif(24.8 MB),record-replay.gif(2.5 MB)Lighthouse audit (before, on live main)