A Centralized Pain in the Config
Sadly, Docusaurus 2.4.0 is still stuck in the past - and uses react-router-config - which has pretty much became obsolete since react-router version 4, which released in 2017 (latest version is v6 and docusaurus uses v5).
The end result is that EVERY SITE ROUTE is forced onto the main client bundle, even for the homepage! Maybe you're lucky, and your site only has a few hundred routes with relatively short url paths.
But imagine how this scales for thousands of routes...
Well, ifullstack.dev wiki has OVER 9000 ROUTES!! (There's actually over 11K routes, but I wanted to keep the meme alive.)
But never fear, you can always patch-package your way out of dependency hell?!
Is your production main.js bundle also spammed by tons of routes?
Inspecting my production main.js bundle - reveals over 6 MEGABYTES worth of repetitive routes just from the router config object alone!!
Even though I configured brotli compression for all assets - and the actual bandwidth to transfer the main.js bundle is ~1MB - it still murders the browsing experience for 3G mobile users. The main bundle still needs to be evaluated on a single blocking thread (especially if you haven't set up any web workers to paralellize the load). Also, Google Lighthouse metrics emulate mobile browsing experience on throttled CPU. All this is to say that don't fill up your main.js bundle with lots of complex, compute-expensive code.
It is unclear whether docusaurus requires this module to perform sitemap generation - or any other critical static type analyses. However, such routes are ideal candidates for lazy-loading. This is actually another prime example of how web pages have regressed in performance after adding React into them, due to bad configuration and using out-of-date libraries.
How to patch Docusaurus & decentralize the centralized routing config?
The quick solution would be to just cut sidebar functionality out. This certainly may benefit the mobile version more than for desktop. But that would be a cop-out!
Patching Docusaurus to Suspense and Lazy load routes
The official documentation recommends swizzling to eject or modify components through wrappers. In my experience, this is extra complicated, painful to fix dependencies & downright unnecessary. The obvious solution is to directly patch-package all the mistakes or add your custom code directly to the source. You also need to do this or something similar, if you intend to submit pull requests.
Rename your sidebar to 1-letter!
If you left the default sidebar name of "tutorialSidebar" in your sidebars.js - expect this to massively inflate your prod main.js bundle!
For a 11K+ route site like ifullstack.dev - with full auto-generation of the sidebar tree- this one reduced the main bundle by 187.92KB!
Disable Sidebar & Breadcrumbs
Before: 7.53MB
After: 3.39MB
import React from 'react';
import '@generated/client-modules';
-import routes from '@generated/routes';
import { useLocation } from '@docusaurus/router';
-import renderRoutes from '@docusaurus/renderRoutes';
import Root from '@theme/Root';
import SiteMetadata from '@theme/SiteMetadata';
import normalizeLocation from './normalizeLocation';
import { BrowserContextProvider } from './browserContext';
import { DocusaurusContextProvider } from './docusaurusContext';
-import PendingNavigation from './PendingNavigation';
-import BaseUrlIssueBanner from './BaseUrlIssueBanner';
+const PendingNavigation = React.lazy(()=>import('./PendingNavigation'));
+const BaseUrlIssueBanner = React.lazy(()=>import('./BaseUrlIssueBanner'));
import SiteMetadataDefaults from './SiteMetadataDefaults';
+const LazyRoute = React.lazy(()=>import("./LazyRoute"))
// TODO, quick fix for CSS insertion order
// eslint-disable-next-line import/order
import ErrorBoundary from '@docusaurus/ErrorBoundary';
export default function App() {
- const routeElement = renderRoutes(routes);
const location = useLocation();
return (<ErrorBoundary>
<DocusaurusContextProvider>
@@ -29,10 +27,16 @@ export default function App() {
<Root>
<SiteMetadataDefaults />
<SiteMetadata />
- <BaseUrlIssueBanner />
- <PendingNavigation location={normalizeLocation(location)}>
- {routeElement}
- </PendingNavigation>
+ <React.Suspense fallback={null}>
+ <BaseUrlIssueBanner />
+ </React.Suspense>
+ <React.Suspense fallback={null}>
+ <PendingNavigation location={normalizeLocation(location)}>
+ <React.Suspense fallback={null}>
+ <LazyRoute />
+ </React.Suspense>
+ </PendingNavigation>
+ </React.Suspense>
</Root>
</BrowserContextProvider>
</DocusaurusContextProvider>
diff --git a/node_modules/@docusaurus/core/lib/client/LazyRoute.ts b/node_modules/@docusaurus/core/lib/client/LazyRoute.ts
new file mode 100644
index 0000000..e2fd17c
Refactor URL paths to minimize bundle
Another option could be to refactor duplicated URL path segments - and concatenate these strings back into routeConfig. Similar to the CSS mangle script - it could be feasible to mutate the final main.js bundle. But the question is whether this would really improve TBT - lighten the load on the main thread, since it is still essentially the same config object. Also, the brotli/gzip compression probably already takes care of the duplicated URL paths - so the transfer size wouldn't necessarily improve by much or at all.
