In today's digital landscape, website performance is crucial for user experience and search engine rankings. A fast, responsive site not only keeps visitors engaged but also improves conversion rates and overall satisfaction. One of the most reliable and accessible tools for measuring web performance is 'Lighthouse', an open-source, automated web tool developed by Google. Lighthouse analyses web apps, providing scores on Performance, Accessibility, Best Practises, and SEO.
Recently I was on a mission to optimize my Next.js website (github repo), aiming for perfect Lighthouse score. Here's how I achieved this feat and the lessons I learned along the way.
1. Image Optimization
I used Next.js's built-in Image component. It's automatically handling lazy loading, resizing, and serving images in modern formats like WebP when supported by the browser.
<Image
width={100}
height={113}
alt="thumbnail"
loading="lazy"
src={`/images/${article.image}`}
className="rounded transition border-gray-500 group-hover:border-gray-500 sm:order-1 sm:col-span-2 sm:translate-y-1"
/>
2. Code Minification
Next.js automatically minifies JavaScript in production builds. I ensured this feature was enabled in my next.config.js:
module.exports = {
reactStrictMode: true,
swcMinify: true,
};
3. Caching Implementation
Next.js provides efficient caching strategies out of the box. By relying on these default behaviors, I ensured that my site was making the most of browser and server-side caching without any additional configuration.
4. Reducing Server Response Time: Static Generation FTW
I utilized Next.js's static generation capabilities for most of my content. This approach pre-renders pages at build time, significantly reducing server response time:
useEffect(() => {
fetch("/api/data")
.then((response) => response.json())
.then((fetchedData: Data) => setData(fetchedData));
}, []);
5. Eliminating Render-Blocking Resources
Next.js automatically handles code splitting and lazy loading of JavaScript modules. I took advantage of this by using dynamic imports for components that weren't immediately necessary:
import dynamic from 'next/dynamic'
import Loading from './Loading'
const DynamicComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <Loading />,
})
6. Lazy Loading on Skeleton loaders
While the Image component handled lazy loading for images, I implemented lazy loading for other content as well. I created a reusable Loading component to display while content was being fetched:
const Loading = memo(() => {
return (
<div className="space-y-3 animate-pulse mt-24 mb-12">
{/* Loading skeleton */}
</div>
);
});
7. CDN Usage: Vercel's Global Network
By deploying my Next.js app on Vercel, I automatically benefited from their global CDN, ensuring fast content delivery regardless of the user's location.
8. Mobile Optimization
I used Tailwind CSS for its responsive design utilities, ensuring my site looked great on all devices:
<div className="mx-auto min-h-screen max-w-screen-xl px-6 py-12 font-sans md:px-12 md:py-20 lg:px-24 lg:py-0 text-white">
<div className="lg:flex lg:justify-between lg:gap-4">
{/* Content */}
</div>
</div>
Additional Optimizations
- I used modern React patterns like hooks and functional components for better performance and readability.
- Implemented performance monitoring tools like Vercel Analytics and Speed Insights.
<Analytics />
<SpeedInsights />
- Utilized Next.js's file-based routing system for efficient navigation.
- Adopted TypeScript for improved code quality and fewer runtime errors.
After all, it was a rewarding process to see all 100 on the scoreboard on Lighthouse. Have you optimized your Next.js project? What strategies worked best for you? Share your experiences in the comments below!
Top comments (0)