
You've built a sleek Next.js application that runs beautifully on desktop browsers. The Lighthouse scores are impressive, and everything feels snappy and responsive. But when you test on mobile, it's a different story - performance scores plummet to 60-65, and your Largest Contentful Paint (LCP) crawls past 2.5 seconds.
If this sounds familiar, you're not alone. Many Next.js developers face this exact challenge, where mobile performance significantly lags behind desktop metrics. The good news? With the right optimizations and strategies, you can dramatically improve your mobile Lighthouse scores without compromising your application's functionality.
Understanding the Mobile Performance Gap
Before diving into solutions, it's crucial to understand why mobile performance often suffers compared to desktop. Mobile devices typically have:
Less processing power
Limited memory
Slower network connections
Different rendering behaviors
These limitations mean that performance optimizations that work well on desktop might not be enough for mobile users. Additionally, mobile users are more likely to access your site under suboptimal conditions - on spotty connections, older devices, or while multitasking.
Core Web Vitals: The Key Metrics
Google's Core Web Vitals are especially important for mobile performance. These metrics include:
Largest Contentful Paint (LCP): Measures loading performance. For a good user experience, LCP should occur within 2.5 seconds of when the page first starts loading.
First Input Delay (FID): Measures interactivity. Pages should have a FID of less than 100 milliseconds.
Cumulative Layout Shift (CLS): Measures visual stability. Pages should maintain a CLS of less than 0.1.
On mobile devices, these metrics become even more critical. A layout shift that seems minor on desktop can be particularly frustrating on a smaller screen, while slow loading times can lead to higher bounce rates from mobile users.
Common Mobile Performance Killers
Through extensive research and developer feedback, we've identified several common issues that particularly impact mobile performance:
Oversized images being served to mobile devices
Excessive JavaScript bundles and static chunks
Render-blocking CSS and JavaScript
Unoptimized third-party scripts (especially ads and analytics)
Poor font loading strategies
In the following sections, we'll explore practical solutions to each of these challenges, with specific Next.js optimizations that can help you achieve better mobile performance scores.
1. Optimizing Images for Mobile
Image optimization is one of the most impactful ways to improve mobile performance. As one developer pointedly noted, "DONT LOAD A 5000px WIDE IMAGE WHEN ITS BEING DISPLAYED AT 400px." This common mistake can severely impact your mobile performance scores.
Using Next.js Image Component
Next.js provides a powerful Image
component that automatically handles many optimization tasks:
import Image from 'next/image'
function MyComponent() {
return (
<Image
src="/my-image.jpg"
alt="Description"
width={400}
height={300}
priority={true} // For above-the-fold images
/>
)
}
Key features of the Image component include:
Automatic image resizing for different devices
Modern format conversion (WebP/AVIF)
Lazy loading by default
Preventing layout shift through proper sizing
Best Practices for Mobile Image Optimization
Properly Size Images
Use responsive sizes with the
sizes
propImplement proper breakpoints for different devices
<Image src="/hero.jpg" sizes="(max-width: 768px) 100vw, 50vw" fill priority />
Use Modern Formats
Enable WebP format in your next.config.js:
module.exports = { images: { formats: ['image/webp'], }, }
Implement Proper Loading Strategies
Use
priority
for above-the-fold imagesLazy load images below the fold
Consider using blur placeholder for better perceived performance
2. JavaScript Optimization
One of the most common complaints from developers is the number of static chunk scripts loading on mobile devices. Here's how to address this issue:
Bundle Analysis and Optimization
First, install and configure the bundle analyzer:
npm install @next/bundle-analyzer
In your next.config.js
:
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
// your existing config
})
Implementing Dynamic Imports
Replace heavy static imports with dynamic ones:
// Before
import HeavyComponent from './HeavyComponent'
// After
import dynamic from 'next/dynamic'
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <p>Loading...</p>
})
Reducing JavaScript Payload
Code Splitting
Use route-based code splitting
Implement component-level code splitting for large features
Tree Shaking
Enable proper tree shaking in your build process
Remove unused exports
// next.config.js module.exports = { webpack: (config, { dev, isServer }) => { if (!dev && !isServer) { config.optimization.minimize = true; } return config }, }
3. CSS Optimization
CSS optimization is crucial for mobile performance, especially when using frameworks like Tailwind CSS. Many developers report issues with unused CSS blocking rendering and impacting performance.
Optimizing Tailwind CSS
Purge Unused Styles
// tailwind.config.js module.exports = { content: [ './pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}', ], // other config }
Split CSS by Route
Use CSS Modules for route-specific styles
Implement dynamic importing for CSS when possible
Critical CSS Extraction
Implement critical CSS extraction to improve First Contentful Paint:
// next.config.js
module.exports = {
experimental: {
optimizeCss: true, // This enables CSS optimization
},
}
4. Font Optimization
Font loading can significantly impact mobile performance. As one developer noted, "removing fonts improved performance score from 64 to 79."
Implementing Efficient Font Loading
Use Next.js Font Optimization
import { Inter } from 'next/font/google' const inter = Inter({ subsets: ['latin'], display: 'swap', })
Consider System Fonts
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
Font Loading Best Practices
Use
font-display: swap
for better perceived performancePreload critical fonts
Consider variable fonts for multiple weights
5. Third-Party Script Management
Third-party scripts, especially ads and analytics, can severely impact mobile performance. Here's how to optimize them:
Script Loading Strategies
Use Next.js Script Component
import Script from 'next/script' export default function Layout() { return ( <> <Script src="https://www.googletagmanager.com/gtag/js" strategy="lazyOnload" /> </> ) }
Implement Proper Loading Strategies
beforeInteractive
: Critical scriptsafterInteractive
: UI enhancement scriptslazyOnload
: Low-priority scripts
Optimizing Google AdSense and Analytics
Defer Non-Critical Scripts
<Script src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" strategy="lazyOnload" crossOrigin="anonymous" />
Consider Using Partytown
Move third-party scripts to web workers
Reduce main thread blocking
// next.config.js const withPartytown = require('@builder.io/partytown/utils') module.exports = withPartytown({ // your config })
6. Server-Side Optimization
Server-side optimizations can significantly impact mobile performance by reducing the time to first byte (TTFB) and improving overall response times.
Implementing Caching Strategies
Static Generation
// pages/blog/[slug].js export async function getStaticProps({ params }) { const post = await getPost(params.slug) return { props: { post }, revalidate: 60, // Revalidate every minute } }
Incremental Static Regeneration (ISR)
Use ISR for dynamic content that doesn't need real-time updates
Balance between static generation and server-side rendering
API Route Optimization
Edge Runtime
export const config = { runtime: 'edge', }
Response Caching
export default async function handler(req, res) { res.setHeader('Cache-Control', 's-maxage=10, stale-while-revalidate') // Your API logic here }
7. Monitoring and Testing
Regular monitoring is crucial for maintaining good mobile performance scores.
Implementation of Web Vitals Monitoring
export function reportWebVitals(metric) {
if (metric.label === 'web-vital') {
console.log(metric) // Send to analytics
}
}
Testing Best Practices
Regular Lighthouse Audits
Test on various mobile devices and networks
Use Chrome DevTools' mobile emulation
Monitor Core Web Vitals regularly
Performance Budget
// next.config.js module.exports = { experimental: { performanceBudget: { maxSize: { javascript: '170kb', css: '50kb', }, }, }, }
8. Advanced Optimization Techniques
For those looking to squeeze out every last performance point, here are some advanced optimization techniques:
Implementing Service Workers
Next PWA Setup
// next.config.js const withPWA = require('next-pwa')({ dest: 'public', register: true, skipWaiting: true, }) module.exports = withPWA({ // your next config })
Custom Cache Strategies
// public/service-worker.js workbox.routing.registerRoute( /^https:\/\/your-api\.com/, new workbox.strategies.StaleWhileRevalidate({ cacheName: 'api-cache', }) )
Resource Hints
Implement resource hints for better performance:
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="anonymous"
/>
<link
rel="dns-prefetch"
href="https://your-api.com"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
Optimizing for Core Web Vitals
Layout Stability
Reserve space for dynamic content
Use CSS aspect ratio boxes
.aspect-ratio-box { position: relative; padding-top: 56.25%; /* 16:9 Aspect Ratio */ } .aspect-ratio-box-inside { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
Input Latency
Debounce event handlers
Use
requestAnimationFrame
for smooth animations
function debounce(func, wait) { let timeout return function executedFunction(...args) { const later = () => { clearTimeout(timeout) func(...args) } clearTimeout(timeout) timeout = setTimeout(later, wait) } }
Conclusion and Best Practices
Improving mobile Lighthouse scores in Next.js applications requires a comprehensive approach that addresses multiple aspects of performance optimization. Here's a final checklist to ensure you're covering all bases:
Quick Wins Checklist
✅ Implement Next.js Image component for all images ✅ Enable font optimization with next/font ✅ Implement proper script loading strategies ✅ Configure Tailwind purge for production ✅ Set up proper caching headers
Long-term Strategies
Regular Monitoring
Set up continuous performance monitoring
Track Core Web Vitals in production
Regular Lighthouse audits on mobile devices
Performance Culture
Establish performance budgets
Review third-party scripts regularly
Document optimization strategies
Mobile-First Development
Test on real mobile devices
Optimize for low-end devices
Consider offline capabilities
Remember, mobile performance optimization is an ongoing process, not a one-time fix. Keep monitoring your metrics, stay updated with Next.js best practices, and continuously iterate on your optimizations.
By following these guidelines and implementing the suggested optimizations, you can significantly improve your Next.js application's mobile Lighthouse scores and provide a better experience for your mobile users.
Additional Resources
Start implementing these optimizations today, and watch your mobile Lighthouse scores improve dramatically!