Strategies for Optimizing Your JavaScript Bundle for Better Web Performance
⏳ 5 min read
📝 479 words
Strategies for Optimizing Your JavaScript Bundle
You can use the following techniques to optimize your JavaScript bundle:
Ideal Javascript Loading
- To start, load only critical Javascript to get the app framework up and running
- Next, load necessary Javascript modules for functionality
- Conditionally import ESM modules only when they are needed (conditionally loading modules = significant perf benefits) when modules update, the browser only needs to download the new module, not all the Javascript on the site.
Javascript Optimization
- Minify to reduce size
- "Uglify" to improve code efficiency
- Code split and use ESM modules when possible
Audit your js bundle size
I wrote this article on /automating-nextjs-bundle-size-monitoring to set up bundle size monitoring in your Next.js app.
Lazy Load Javascript
ESM Module and lazy loading support :
- Webpack
- Snowpack
- Parcel
- Rollup
Dynamic Import
dynamic import, it wont affect the initial page load size
import doSomethingCool from '../util.test'
async function onButtonClick() {
const doSomethingCool = await import('../util/test'); // dynamic import, lazy load
doSomethingCool.default();
}
Using Intersection Observers
You might use intersection obsever to wait until a component comes in to the view and then load the javascript at that point
const observer = new IntersectionObserver((entries) => {
entries.forEach(async (entry) => {
if (entry.isIntersecting) {
const { helper } = await import ('more/crappy/js') // lazy load
}
})
})
Create a custom hook to use intersection observer in react
In the example below, we have a LazyLoad component that uses the Intersection Observer API to load a heavy component only when it comes into the viewport.
import { useEffect, useRef, useState } from "react";
import { LazyLoad } from "../components/lazy-load";
import dynamic from "next/dynamic";
const HeavyComponent = dynamic(() => import("../components/heavy-component"), {
ssr: false,
});
export default function Home() {
return (
<div className="min-h-screen flex flex-col items-center justify-center py-2">
<h1 className="text-4xl font-bold mb-8">Lazy Load Example</h1>
<div className="w-full max-w-2xl">
<p className="mb-4">
Scroll down to load the heavy component...
</p>
<div style={{ height: "800px" }}></div> {/* Spacer to enable scrolling */}
<LazyLoad>
<HeavyComponent />
</LazyLoad>
</div>
</div>
);
}
LazyLoad Component
"use client";
import { useEffect, useRef, useState } from "react";
interface LazyLoadProps {
children: React.ReactNode;
className?: string;
}
export const LazyLoad = ({ children, className }: LazyLoadProps) => {
const [isVisible, setIsVisible] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{
rootMargin: "50px",
}
);
if (ref.current) {
observer.observe(ref.current);
}
return () => {
observer.disconnect();
};
}, []);
return (
<div ref={ref} className={className}>
{isVisible ? (
children
) : (
<div className="animate-pulse flex space-y-4">
<div className="flex-1 space-y-4 py-1">
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<div className="space-y-2">
<div className="h-4 bg-gray-200 rounded"></div>
<div className="h-4 bg-gray-200 rounded w-5/6"></div>
</div>
</div>
</div>
)}
</div>
);
};
