• Next.js performance optimization becomes increasingly important in large-scale projects where application complexity and traffic grow significantly.
• Large Next.js applications often face performance challenges related to bundle size, inefficient data fetching, and unnecessary client-side rendering.
• Careful architecture decisions, including modular code structure and optimized rendering strategies, help maintain performance as projects scale.
• Monitoring performance metrics and analyzing real-world usage data helps teams identify bottlenecks in complex applications.
• Development teams should continuously review dependencies, optimize assets, and improve caching strategies to prevent performance degradation.
• Lessons learned from large production projects can help developers build faster, more maintainable Next.js applications at scale.
Introduction to Next js Performance Optimization
At Pagepro, we’ve spent years developing and optimizing high-traffic Next.js websites and apps for our clients. Along the way, we’ve faced numerous challenges and learned crucial lessons about Next js performance optimization. Recently, we conducted an in-depth survey across our development, QA, and project management teams to distill our collective knowledge into actionable insights.
As a result today we’re sharing our top five lessons for for Next.js performance optimization in large projects. These hard-earned insights will help you avoid common pitfalls and improve scalability, reduce costs associated with slow load times, and enhance user satisfaction—driving both technical and business success.
If you prefer video content, check out the video I recorded with our CTO, Jakub Dakowicz:
Lesson 1: Measure Performance with Chrome DevTools and Core Web Vitals
The Performance Tab in Chrome DevTools is often overlooked, but it’s a goldmine of information for performance optimization. Our lead developer, Jakub, admits that he initially found it overwhelming, but with practice, it became an indispensable tool in our optimization toolkit.
At first, I tried to convince myself that there was nothing useful in the Performance Tab, but I was simply intimidated by the data I didn’t understand at the time. Now, I see how much valuable information it provides about your app, and I use it every day. I highly recommend it to everyone.
Jakub Dakowicz, CTO at Pagepro
Key Benefits of the Performance Tab:
Precise Performance Peaks Identification: Pinpoint exact functions causing performance spikes.
Comprehensive Execution Analysis: View all network requests and code executions on the main thread.
Detailed Asset Loading Insights: Track when and how assets are loaded.
Core Web Vitals Monitoring: Keep an eye on crucial metrics like First Contentful Paint (FCP) and Largest Contentful Paint (LCP).
Pro Tip: Name your functions instead of using anonymous ones. This practice not only follows good coding standards but also makes it easier to identify problematic functions in the Performance Tab.
Get Next.js Vercel Cost Optimization Guide
Learn real-life techniques to optimize the hosting costs of your Next.js apps.
Thank you for signing up. Check your e-mail for the guide.
Real-world Impact
We once had a carousel component that was taking a whopping two seconds of main thread work – a significant performance bottleneck. Using the Performance Tab, we identified the issue and replaced the library with a more CSS-focused one (React Snapscroll). The result? We slashed the execution time to just 200 milliseconds – a 10x improvement! It influences the user experience but also improves site responsiveness, helping the client maintain engagement and avoid potential revenue loss from slow performance.
But it doesn’t stop there. The Performance Tab also helped us uncover and fix an Interaction to Next Paint (INP) issue with our image lightbox component. We found that lazy-loading the component was causing a noticeable freeze when users clicked on images. By adding a simple loading indicator, we significantly improved the perceived performance and user experience.
Lesson 2: Automate Performance Monitoring with Lighthouse CI
One of the most significant mindset shifts we’ve made at Pagepro is moving away from treating performance optimization as a final step in the development process. Previously, addressing performance at the end of development often led to costly rework, bottlenecks, and technical debt. Now, by embedding performance checks throughout the entire development cycle, we’ve minimized the risk of late-stage surprises and created a more efficient, data-driven approach to optimization.
Why Continuous Monitoring Matters:
Early Problem Detection: Catch performance issues before they compound and become harder to fix.
Consistent Performance: Ensure new features don’t negatively impact overall site performance.
Data-Driven Development: Make informed decisions based on ongoing performance metrics.
How We Implement Continuous Performance Monitoring at Pagepro:
1. Pre and Post-Merge Checks:
Before merging: Developers run performance checks on their local environment.
After merging: Automated checks run in the staging environment.
2. Automated CI/CD Pipeline:
We use Lighthouse CI integrated into our GitHub Actions workflow.
Every pull request triggers performance tests, flagging any regressions.
3. Comprehensive Metrics Focus:
Core Web Vitals (LCP, FID, CLS, INP)
Time to Interactive (TTI)
Total Blocking Time (TBT)
Core Web Vitals visible in Performance Tab of Chrome DevTools
4. Advanced Tooling:
React DevTools Profiler for component-level performance analysis
Vercel Analytics for tracking Core Web Vitals across all our projects
5. Regular Review Process:
Weekly team performance reviews to discuss trends and address consistent issues
Monthly performance reports for clients on SLA projects
6. Custom Performance Dashboard:
We’ve developed an internal dashboard that aggregates performance data across all our projects.
This allows us to spot trends, compare projects, and identify best practices.
Actionable Advice:
Start small: Begin with basic Lighthouse checks in your CI/CD pipeline.
Gradually increase complexity: Add more detailed checks and custom metrics as your team becomes more comfortable with the process.
Make performance data accessible: Create dashboards or reports that all team members can easily understand and act upon.
Set performance budgets: Establish thresholds for key metrics and automatically flag when they’re exceeded.
Understanding the fundamentals of Next.js and how it handles server-side and client-side code is crucial for optimization. Once you grasp this, the next step is to focus on reducing the amount of code running on the client.
Keeping client-side code to a minimum is essential because it directly impacts load times, reduces the bundle size, and ensures faster, more responsive user experiences. Too much code on the client can overwhelm the browser, leading to performance bottlenecks and degraded user interactions.
Client-Side Rendering
Key Considerations:
Code Distribution: Be aware of what’s served to the client, what runs on the server, and what’s generated during the build process.
Import Management: Carefully manage imports to avoid accidentally including server packages on the client-side bundle.
Bundle Analysis: Regularly use tools like Bundle Analyzer to visualize your bundle composition and identify opportunities for optimization.
Strategies for Optimizing Client-Side Code:
Lazy Loading: Implement lazy loading for below-the-fold components or those triggered by user interactions.
Code Splitting: Utilize Next.js’s automatic code splitting features and add custom split points where necessary.
Tree Shaking: Ensure your build process effectively eliminates dead code.
Optimizing Images with the Next.js Image Component
If there’s one Next.js-specific optimization that consistently delivers the biggest immediate impact, it’s switching from standard <img> tags to the built-in next/image component. In our large-scale projects at Pagepro, this single change has measurably improved Core Web Vitals scores – particularly LCP (Largest Contentful Paint) – without requiring significant refactoring effort.
The next/image component handles several performance concerns automatically: lazy loading by default, format conversion to WebP where the browser supports it, responsive sizing via srcset, and prevention of layout shift through required widthand height props. These aren’t features you need to configure – they’re on out of the box.
That said, getting the most out of the Image component requires knowing which props to use deliberately:
priority – Add this to any image that appears above the fold (hero images, LCP candidates). Without it, Next.js will lazy-load the image, which actively hurts LCP scores on critical content.
placeholder=”blur” – Provides a blurred preview while the full image loads. For local images, Next.js generates the blur automatically. For remote images, you’ll need to supply a blurDataURL.
sizes – Tells the browser which image size to request at different viewport widths. Without this, Next.js defaults to 100vw, which can result in downloading images far larger than needed on smaller screens.
fill – Useful for images inside containers with dynamic dimensions (e.g., card grids). Pair it with position: relative on the parent.
One mistake we see frequently in projects we inherit: developers treat the Image component as a drop-in replacement for <img> and leave it at that. The component does a lot on its own, but leaving sizes undefined on a responsive layout, or forgetting priority on a hero image, erases much of the performance benefit.
Real-world example: On one client project – a content-heavy marketing site with image-rich landing pages – switching from raw <img> tags to properly configured next/image (with correct sizes attributes and priority on hero images) reduced LCP by an average of 1.2 seconds across key landing pages. That improvement directly translated to a measurable drop in bounce rate on mobile.
For remote images, remember to configure the remotePatterns array in next.config.js – Next.js will block unconfigured external image domains by default. This is a security measure, not a bug, but it catches teams off guard when images silently fail in production.
Actionable advice:
Audit every <img> tag in your codebase and evaluate whether next/image is appropriate – in most cases, it is.
Always set priority on the first visible image on any page, especially hero banners.
Define sizes for any image that doesn’t fill 100% of the viewport width. A value like sizes=”(max-width: 768px) 100vw, 50vw” makes a meaningful difference on mobile.
Use placeholder=”blur” on large above-the-fold images to reduce perceived load time.
Analyzing Your Bundle and Splitting Code Effectively
Most performance problems in large Next.js projects don’t announce themselves – they accumulate quietly as your codebase grows. Bundle bloat is one of the most common culprits, and it’s also one of the easiest to miss without the right tooling in place. At Pagepro, bundle analysis is a standard step in our development workflow, not something we revisit only when performance complaints come in.
The first tool to add to your setup is @next/bundle-analyzer. It wraps your Next.js build and generates an interactive treemap showing exactly what’s inside each JavaScript bundle – which packages, how large they are, and whether they’re being loaded on routes that don’t need them.
Run ANALYZE=true npm run build and the treemap opens automatically in your browser. What you’re looking for: large dependencies appearing in shared chunks, server-only packages leaking into client bundles, and duplicated modules being loaded more than once.
Code splitting is the natural follow-up to bundle analysis – once you know what’s bloating your bundles, dynamic imports give you a surgical way to fix it. Next.js supports next/dynamic for component-level splitting:
The ssr: false flag is worth understanding clearly: it tells Next.js not to render this component on the server at all, which is appropriate for browser-only libraries (charting tools, rich text editors, map components) but should not be used as a default. Disabling SSR unnecessarily can hurt your LCP and SEO.
Tree shaking – the build process that strips unused code – works automatically in Next.js production builds, but only if your imports are structured to allow it. Named imports from well-structured packages tree-shake cleanly. Default imports from large utility libraries often don’t. For example:
// Imports the entire lodash library - tree shaking won't help here
import _ from 'lodash';
// Imports only the function you need - bundle-friendly
import debounce from 'lodash/debounce';
Real-world example: On one project we inherited, bundle analysis revealed that a PDF generation library was being loaded on every page – including simple landing pages that never generated a PDF. Moving it behind a dynamic import reduced the initial JS payload by 40% on those routes, with a corresponding improvement in Time to Interactive.
Actionable advice:
Install @next/bundle-analyzer and run it before every major release – treat large unexpected additions to the bundle as a regression, not an inevitability.
Use next/dynamic for any component that is heavy, browser-only, or conditionally rendered based on user interaction.
Audit your import style in shared components – named imports over default imports wherever the library supports it.
Set a bundle size budget in your CI/CD pipeline and fail the build if it’s exceeded. Next.js supports this natively via the experimental.bundlePagesExternals config and third-party size-limit tools.
Controlling Third-Party Scripts and Fonts with Next.js Built-in Components
Third-party scripts and custom fonts are two of the most reliable ways to silently damage your Core Web Vitals scores. They’re added incrementally – an analytics tag here, a custom typeface there – and by the time the performance impact is visible, the cause is buried under months of development. Next.js provides dedicated components for both, and using them correctly is one of the higher-leverage optimizations available to any team.
Managing Third-Party Scripts with next/script
The standard <script> tag gives you limited control over when a script loads relative to your page content. The next/scriptcomponent adds a strategy prop that lets you define loading behavior explicitly:
import Script from 'next/script';
// Loads after the page is interactive - right for most analytics tools
<Script src="https://example.com/analytics.js" strategy="afterInteractive" />
// Loads after the browser has finished all work - right for non-critical scripts
<Script src="https://example.com/widget.js" strategy="lazyOnload" />
// Loads before any page content - use sparingly, only when genuinely required
<Script src="https://example.com/critical.js" strategy="beforeInteractive" />
The most common mistake we see is defaulting to beforeInteractive for convenience – or worse, leaving third-party scripts as plain <script> tags in _document.js. Both approaches block rendering and directly inflate Total Blocking Time (TBT) and INP scores. In practice, the vast majority of marketing and analytics scripts work perfectly with afterInteractive.
On one client project running a high volume of third-party tags – a mix of analytics, heatmapping, and a live chat widget – switching all scripts to next/script with appropriate strategies reduced Total Blocking Time by over 300ms. That single change pushed their INP score from “Needs Improvement” into the “Good” threshold.
Eliminating Font Loading Delays with next/font
Custom fonts loaded via Google Fonts or self-hosted @font-face declarations introduce two performance problems: an extra network request that blocks rendering, and layout shift as the fallback font is replaced by the loaded typeface. next/font solves both.
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
What next/font does behind the scenes: it downloads the font files at build time, self-hosts them alongside your application assets, and eliminates the external request to Google’s servers entirely. It also generates an optimized fallback font with adjusted metrics to minimize layout shift during load – which directly improves your CLS (Cumulative Layout Shift) score.
The display: ‘swap’ setting ensures text remains visible during font loading using the fallback, rather than showing invisible text. For most projects this is the right default, though for brand-critical typography where flash of unstyled text is unacceptable, display: ‘optional’ is worth considering – it skips the custom font entirely if it hasn’t loaded within a tight deadline.
Actionable advice:
Audit every <script> tag in your _document.js and _app.js – replace them with next/script and assign the most permissive strategy that still meets the script’s functional requirements.
Default to afterInteractive for analytics, tag managers, and marketing pixels. Reserve beforeInteractive for scripts that genuinely cannot function otherwise.
Replace any Google Fonts <link> tags with next/font/google – the performance gain is immediate and the migration is straightforward.
If you’re self-hosting fonts already, switch to next/font/local to retain the layout shift prevention benefits without the external request overhead.
Real-world Example
We once worked on a project where a large data processing library was accidentally imported on the client-side, significantly increasing the bundle size. By moving this processing to an API route on the server, we reduced the client-side bundle by 30% and improved load times across the site.
Actionable Advice:
Audit your imports regularly, especially in shared components that might be used in both server and client contexts.
Set up Bundle Analyzer as part of your build process and review it before major releases.
Create a checklist for code reviews that includes checking for appropriate code splitting and lazy loading implementation.
EXPERT INSIGHTS, FRICTIONLESSLY DELIVERED!
Curated tech news delivered straight to your inbox every month.
Lesson 4: Build a Performance-First Development Culture
Delivering consistently high-performance applications requires everyone, from developers to project managers, to focus on performance at every stage. At Pagepro, we’ve made performance optimization a shared responsibility, where continuous learning and improvement are at the core. Each team member plays a crucial role in embedding performance considerations throughout the development process, ensuring that our applications are fast, efficient, and scalable from the ground up.
Key Aspects of a Performance-First Culture:
Shared Responsibility: Everyone, from designers to developers to project managers, plays a role in performance optimization.
Continuous Learning: Regular knowledge sharing and staying updated on the latest performance best practices.
Performance-Centric Processes: Integrating performance considerations into every stage of the development lifecycle.
How We Build a Performance-First Culture at Pagepro:
1. Project Kickoff:
Set specific, measurable performance goals for each project.
Discuss potential performance challenges and strategies to address them.
Maintain a knowledge base of performance-friendly libraries and practices.
Implement pair programming sessions focused on performance optimization.
4. Code Reviews:
Include performance-specific checklist items in our code review process.
Use automated tools to flag potential performance issues before human review.
5. Regular Performance Workshops:
Conduct monthly “Performance Deep Dives” where we analyze a specific project or technology.
Encourage team members to share their performance wins and lessons learned.
6. Performance Recognition Program:
Implement a point system for performance improvements.
Celebrate team members who significantly enhance project performance.
7. Client Education:
Educate clients about the importance of performance and its impact on their business goals.
Provide regular performance reports and recommendations for ongoing improvements.
Actionable Advice:
Start with education: Conduct workshops to ensure all team members understand the basics of web performance and its importance.
Lead by example: Have senior team members champion performance best practices.
Make it measurable: Set team and individual OKRs (Objectives and Key Results) related to performance improvements.
Create feedback loops: Regularly gather insights from team members on performance challenges and successes.
Lesson 5: Minimize Data Transfer with ISR, Caching, and GraphQL
For high-performance applications, reducing unnecessary data transfer is key to improving speed and efficiency. Optimizing data transfer improves load times, reducing server costs, and ensuring a smooth user experience. At Pagepro, we’ve developed several strategies to minimize data transfer and improve application performance.
Key Strategies for Data Transfer Optimization:
Minimize Over-fetching:
Implement GraphQL for more granular control over data fetching.
Use REST API effectively by onlyrequesting necessary fields.
Efficient Pagination:
Implement server-side pagination instead of loading all data upfront.
Use cursor-based pagination for large datasets to improve efficiency.
Caching Strategies:
Implement intelligent caching for data that doesn’t change frequently.
Use service workers for offline caching and faster subsequent loads.
Optimized State Management:
Use efficient state management solutions like React Query or SWR.
Implement optimistic UI updates to improve perceived performance.
Compression and Data Formats:
Use Brotli or Gzip compression for API responses.
Consider binary data formats like Protocol Buffers for very large datasets.
Lazy Data Loading:
Implement lazy loading for data associated with off-screen components.
Use intersection observers to trigger data loading as elements come into view.
Leveraging Next.js Features
While focusing on these advanced strategies, don’t forget to leverage Next.js’s built-in optimization features:
Use the Image component for automatic image optimization.
Implement Incremental Static Regeneration (ISR) for dynamic content that doesn’t need real-time updates.
Utilize API Routes for moving data fetching to the server-side where appropriate.
Real-world Impact
On a recent e-commerce project, we implemented a combination of the above strategies. By moving to server-side pagination, implementing efficient caching, and optimizing our GraphQL queries, we reduced initial page load data transfer by 60% and improved Time to Interactive by 45%.
Actionable Advice:
Conduct a data audit: Analyze your application’s data needs and identify opportunities for optimization.
Implement monitoring: Use tools like Datadog or New Relic to track API performance and data transfer metrics.
A/B test optimizations: Gradually roll out data transfer optimizations and measure their impact on real-world performance.
Stay updated: Keep an eye on new Next.js features and updates that can help with data optimization.
Fix Core Web Vitals for Your Next.js Website
Download our free ‘Fix Core Web Vitals: Next.js Optimization Guide for High-Traffic Websites’ and discover actionable strategies to improve performance, boost SEO, and enhance user experience on your Next.js site.
PDF
Check your email for the comprehensive Next.js guide!
FAQ – Frequently Asked Questions About Next.js Performance Optimization
What is the fastest way to improve Next.js performance in an existing project?
Start with bundle analysis using @next/bundle-analyzer to identify the largest contributors to your JavaScript payload – this gives you the highest-impact targets with the least guesswork. From there, replace any standard <img> tags with next/image, switch third-party scripts to next/script with appropriate loading strategies, and migrate fonts to next/font. These four changes address the most common performance bottlenecks without requiring architectural changes to your codebase.
How does the Next.js Image component improve Core Web Vitals?
The next/image component improves Core Web Vitals primarily by reducing LCP (Largest Contentful Paint) and eliminating CLS (Cumulative Layout Shift). It does this by automatically converting images to WebP, generating responsive srcset attributes, lazy loading below-the-fold images by default, and reserving space for images before they load to prevent layout shift. Adding the priority prop to above-the-fold images ensures the browser fetches them early, which directly reduces LCP time on image-heavy pages.
When should I use SSG vs SSR in Next.js for better performance?
Use Static Site Generation (SSG) by default for any page where the content doesn’t change per request – marketing pages, blog posts, documentation, and product listings with infrequent updates. SSG pages are served from a CDN edge with no server computation at request time, making them significantly faster. Use Server-Side Rendering (SSR) only when the page requires real-time, user-specific, or frequently changing data that cannot be cached. For content that updates periodically but doesn’t need to be real-time, Incremental Static Regeneration (ISR) is often the better middle ground – it gives you static performance with controlled freshness.
What is Incremental Static Regeneration (ISR) and when should I use it?
Incremental Static Regeneration (ISR) allows Next.js to regenerate individual static pages in the background after a set time interval, without rebuilding the entire site. You enable it by adding a revalidate value to getStaticProps. ISR is the right choice when your content changes on a predictable schedule – news articles, product prices, event listings – and you want CDN-level performance without serving stale data indefinitely. It eliminates the need to choose between a full static build and a fully dynamic SSR approach.
How do I measure Core Web Vitals for my Next.js application?
The most reliable way to measure Core Web Vitals in a Next.js project is to combine lab data with field data. For lab data, use Lighthouse in Chrome DevTools or Lighthouse CI integrated into your CI/CD pipeline – this gives you consistent, reproducible measurements during development. For real-world field data, Vercel Analytics provides Core Web Vitals tracking per route out of the box, and Google Search Console shows CWV performance aggregated across your actual user base. PageSpeed Insights combines both sources and is useful for a quick per-URL health check.
What are the most common causes of poor LCP in Next.js applications?
The most frequent LCP culprits in Next.js projects are: a large hero image loaded without the priority prop on next/image, causing it to be lazy-loaded when it should be fetched immediately; render-blocking third-party scripts that delay when the browser can paint the page; and oversized JavaScript bundles that extend Time to Interactive. In our experience at Pagepro, missing the priority prop on the above-the-fold image is the single most common fixable LCP issue – and the quickest to resolve.
Does using next/font eliminate the need for font preloading?
Yes – when you use next/font, manual font preloading via <link rel=”preload”> is no longer necessary. Next.js automatically self-hosts the font files at build time and injects the appropriate preload hints into the page <head>. This removes the external network request to font providers like Google Fonts, eliminates the render-blocking behaviour associated with traditional font loading, and handles the preload timing correctly without any manual configuration.
Improving Next js Performance – Conclusion
Optimizing large-scale Next.js websites is an ongoing process that involves many different strategies. Using the right browser tools, continuously monitoring performance, managing server and client-side code effectively, creating a performance-focused culture, and reducing data transfer are all key steps to making your web applications faster and more efficient.
At Pagepro, the above 5 lessonshave been transformative in our approach to building and maintaining high-performance Next.js applications. We’ve seen how these strategies lead to faster load times, better user experiences, and, most importantly, stronger business results for our clients.
Start implementing these lessons in your development cycle, and you’ll be well on your way to creating blazing-fast Next.js applications that delight your users and drive your business forward.
What performance optimization strategies have worked well for your team? Share your experiences in the comments below – let’s learn from each other and push the boundaries of what’s possible with Next.js!
Looking for Experts to Improve Next.js Performance?