perf: lazy-load Algolia DocSearch to reduce initial bundle size and eliminate render-blocking CSS - #5673
Conversation
✅ Deploy Preview for asyncapi-website ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughThe search modal now loads client-side when opened, and DocSearch CSS is injected on demand. Search behavior remains unchanged. Global CSS imports and formatting were normalized. ChangesAlgolia Search
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant AlgoliaSearch
participant documentHead
participant DocSearchModal
AlgoliaSearch->>documentHead: Inject DocSearch CSS when open
AlgoliaSearch->>DocSearchModal: Dynamically load and render while open
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
styles/globals.cssParsing error: ESLint was configured to run on Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
⚡️ Lighthouse report for the changes in this PR:
Lighthouse ran on https://deploy-preview-5673--asyncapi-website.netlify.app/ |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
components/AlgoliaSearch.tsx (1)
331-331: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winPin the deferred stylesheet to the tested DocSearch release.
The repository declares
@docsearch/reactas^3.5.2, but@docsearch/css@3resolves a moving 3.x release from the CDN. The CSS and modal bundle can drift, which makes deployments non-reproducible. Use the exact CSS version validated against the resolved modal package, or bundle that version while keeping it lazy.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/AlgoliaSearch.tsx` at line 331, Update the deferred stylesheet URL in the AlgoliaSearch component to pin `@docsearch/css` to the exact version matching the resolved `@docsearch/react` release, preserving lazy loading and avoiding the moving `@3` CDN reference.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@components/AlgoliaSearch.tsx`:
- Around line 2-26: Run the repository-configured Prettier formatter on
components/AlgoliaSearch.tsx, including the import and DocSearchModal
dynamic-import block and all other changed ranges, without altering behavior.
- Around line 321-355: Update the stylesheet-loading useEffect and the isOpen
rendering flow in the Algolia search component to track DocSearch link load and
error states, resetting readiness when opening and allowing retries after
failures. Render AlgoliaModal only once the stylesheet has fired load
successfully, while preserving the existing modal props and close behavior.
---
Nitpick comments:
In `@components/AlgoliaSearch.tsx`:
- Line 331: Update the deferred stylesheet URL in the AlgoliaSearch component to
pin `@docsearch/css` to the exact version matching the resolved `@docsearch/react`
release, preserving lazy loading and avoiding the moving `@3` CDN reference.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: dee658f6-40de-4fe7-9768-37882191ea23
📒 Files selected for processing (2)
components/AlgoliaSearch.tsxstyles/globals.css
| import type { | ||
| DocSearchHit, | ||
| InternalDocSearchHit, | ||
| StoredDocSearchHit, | ||
| } from '@docsearch/react'; | ||
| import clsx from 'clsx'; | ||
| import dynamic from 'next/dynamic'; | ||
| import Head from 'next/head'; | ||
| import Link from 'next/link'; | ||
| import { useRouter } from 'next/router'; | ||
| import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'; | ||
| import React, { | ||
| createContext, | ||
| useCallback, | ||
| useContext, | ||
| useEffect, | ||
| useRef, | ||
| useState, | ||
| } from 'react'; | ||
| import { createPortal } from 'react-dom'; | ||
|
|
||
| const DocSearchModal = dynamic( | ||
| () => | ||
| import('@docsearch/react').then((mod) => ({ default: mod.DocSearchModal })), | ||
| { ssr: false }, | ||
| ); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Run the configured formatter before merge.
prettier/prettier reports errors in this import and dynamic-import block, with the same issue in many later changed ranges. Format components/AlgoliaSearch.tsx with the repository configuration so lint can pass.
🧰 Tools
🪛 ESLint
[error] 2-6: Replace ⏎··DocSearchHit,⏎··InternalDocSearchHit,⏎··StoredDocSearchHit,⏎ with ·DocSearchHit,·InternalDocSearchHit,·StoredDocSearchHit·
(prettier/prettier)
[error] 12-19: Replace ⏎··createContext,⏎··useCallback,⏎··useContext,⏎··useEffect,⏎··useRef,⏎··useState,⏎ with ·createContext,·useCallback,·useContext,·useEffect,·useRef,·useState·
(prettier/prettier)
[error] 22-24: Replace ⏎··()·=>⏎····import('@docsearch/react').then((mod)·=>·({·default:·mod.DocSearchModal·})), with ()·=>·import('@docsearch/react').then((mod)·=>·({·default:·mod.DocSearchModal·})),·{
(prettier/prettier)
[error] 25-25: Replace ·{·ssr:·false·}, with ·ssr:·false
(prettier/prettier)
[error] 26-26: Insert }
(prettier/prettier)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/AlgoliaSearch.tsx` around lines 2 - 26, Run the
repository-configured Prettier formatter on components/AlgoliaSearch.tsx,
including the import and DocSearchModal dynamic-import block and all other
changed ranges, without altering behavior.
Source: Linters/SAST tools
| useEffect(() => { | ||
| if (isOpen) { | ||
| // Load Algolia CSS on demand when modal first opens | ||
| const linkId = 'docsearch-css'; | ||
|
|
||
| if (!document.getElementById(linkId)) { | ||
| const link = document.createElement('link'); | ||
|
|
||
| link.id = linkId; | ||
| link.rel = 'stylesheet'; | ||
| link.href = 'https://cdn.jsdelivr.net/npm/@docsearch/css@3'; | ||
| document.head.appendChild(link); | ||
| } | ||
| } | ||
| }, [isOpen]); | ||
|
|
||
| return ( | ||
| <> | ||
| <Head> | ||
| <link rel='preconnect' href={`https://${APP_ID}-dsn.algolia.net`} crossOrigin='anonymous' /> | ||
| <link | ||
| rel="preconnect" | ||
| href={`https://${APP_ID}-dsn.algolia.net`} | ||
| crossOrigin="anonymous" | ||
| /> | ||
| </Head> | ||
| <SearchContext.Provider value={{ isOpen, onOpen, onClose, onInput }}>{children}</SearchContext.Provider> | ||
| {isOpen && <AlgoliaModal initialQuery={initialQuery ?? ''} onClose={onClose} indexName={indexName} />} | ||
| <SearchContext.Provider value={{ isOpen, onOpen, onClose, onInput }}> | ||
| {children} | ||
| </SearchContext.Provider> | ||
| {isOpen && ( | ||
| <AlgoliaModal | ||
| initialQuery={initialQuery ?? ''} | ||
| onClose={onClose} | ||
| indexName={indexName} | ||
| /> | ||
| )} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Wait for the DocSearch stylesheet before mounting the modal.
When isOpen becomes true, React mounts AlgoliaModal before the effect appends and loads the stylesheet. The first open can show an unstyled modal. A failed request also prevents retries because the existing element ID is treated as success. Track stylesheet load and error states, and render the modal only after the stylesheet is ready.
🧰 Tools
🪛 ESLint
[error] 340-344: Replace ⏎··········rel="preconnect"⏎··········href={https://${APP_ID}-dsn.algolia.net}⏎··········crossOrigin="anonymous"⏎······· with ·rel='preconnect'·href={https://${APP_ID}-dsn.algolia.net}·crossOrigin='anonymous'
(prettier/prettier)
[error] 346-348: Replace ⏎········{children}⏎······ with {children}
(prettier/prettier)
[error] 349-355: Replace (⏎········<AlgoliaModal⏎··········initialQuery={initialQuery·??·''}⏎··········onClose={onClose}⏎··········indexName={indexName}⏎········/>⏎······) with <AlgoliaModal·initialQuery={initialQuery·??·''}·onClose={onClose}·indexName={indexName}·/>
(prettier/prettier)
🪛 GitHub Check: SonarCloud Code Analysis
[warning] 346-346: The object passed as the value prop to the Context provider changes every render. To fix this consider wrapping it in a useMemo hook.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/AlgoliaSearch.tsx` around lines 321 - 355, Update the
stylesheet-loading useEffect and the isOpen rendering flow in the Algolia search
component to track DocSearch link load and error states, resetting readiness
when opening and allowing retries after failures. Render AlgoliaModal only once
the stylesheet has fired load successfully, while preserving the existing modal
props and close behavior.
|
@asyncapi/bounty_team |
|
Submitted PR #5677 for this issue. |
|
@kenxxxito-cmd, please note that the review of PRs falls to maintainers, and the code submitted is not always in a completed state. |



Problem
Every page on the AsyncAPI website loaded Algolia DocSearch assets upfront, even though the search modal is only displayed after a user explicitly opens it.
This resulted in:
Render-blocking CSS via:
in
globals.css, delaying the browser's first paint and negatively affecting FCP and LCP.Unnecessary JavaScript because
DocSearchModalfrom@docsearch/reactwas statically imported, causing it to be included in the initial JavaScript bundle and increasing parsing time, TBT, and TTI.Every visitor downloading and parsing these assets regardless of whether they ever used the search feature.
On Lighthouse mobile (4× CPU slowdown), this unnecessarily increased the critical rendering path and negatively impacted Core Web Vitals.
Root Cause
globals.csssynchronously imported the Algolia stylesheet using@import, which is render-blocking by specification since the browser must download and parse the stylesheet before painting.AlgoliaSearch.tsxstatically importedDocSearchModal:This prevented webpack from code-splitting the modal, causing it to be bundled into the initial JavaScript payload despite only being needed after explicit user interaction.
Solution
Implemented a two-part optimization to defer both the JavaScript and CSS until the search modal is actually opened.
Remove Render-Blocking CSS
Removed the global stylesheet import from
globals.css:Lazy-load
DocSearchModalReplaced the static import with a dynamic import using
next/dynamic:This creates a separate chunk that is only downloaded when the search modal is first opened.
Load DocSearch CSS On Demand
Since
@docsearch/cssis a CSS-only package and cannot be dynamically imported as JavaScript, the stylesheet is loaded via the DOM when the modal is opened for the first time.The stylesheet is:
Keep Lightweight Functionality Synchronous
The following remain eagerly loaded to preserve instant interactions:
AlgoliaSearchwrapper componentCtrl+K,/,Escape)SearchButtoncomponentThis keeps keyboard shortcuts responsive while deferring the heavy modal implementation.
Testing
Manual
Ctrl+K,/,Escape) work immediatelyPerformance Impact
DocSearchModal@importRelated Issue
Summary by CodeRabbit