New Story

Cut Load Times in Half with These Next.js Tweaks

by Raju DandigamApril 8th, 2025
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Optimizing Next.js applications involves combining improvements in both your code and underlying infrastructure. By minimizing unnecessary re-renders, fine-tuning image handling, employing caching techniques, and integrating server components, you can significantly boost performance.

Coin Mentioned

Mention Thumbnail
featured image - Cut Load Times in Half with These Next.js Tweaks
Raju Dandigam HackerNoon profile picture
0-item

Optimizing Next.js applications involves combining improvements in both your code and underlying infrastructure. By minimizing unnecessary re-renders, fine-tuning image handling, employing caching techniques, using dynamic imports, and integrating server components, you can significantly boost performance, reduce load times, and increase overall efficiency.


Before exploring specific optimizations, it’s important to grasp how Next.js renders and delivers content:

  • Server-Side Rendering (SSR): HTML is generated on every request.
  • Static Site Generation (SSG): Pages are pre-built during the build process.
  • Incremental Static Regeneration (ISR): Static pages are updated after deployment.
  • Client-Side Rendering (CSR): Dynamic content is managed after the initial load.


Each rendering method has unique performance implications, which will be discussed throughout this article.


1. Component-Level Optimizations


Preventing Unnecessary Re-rendersIn React, re-renders can occur unnecessarily when functions defined within the render method are recreated, leading to updates in child components. To avoid this, you can memoize functions with useCallback and wrap components in memo so they only update when their props change. This approach helps cut down on extra computations and boosts performance.


// Bad practice: Creating functions inside render

function ProductCard({ product }) {
  const handleClick = () => {
    console.log('Product clicked:', product.id);
  };
  
  return <div onClick={handleClick}>{product.name}</div>;
}


//  Better approach: Memoize functions and components

import { useCallback, memo } from 'react';

const ProductCard = memo(function ProductCard({ product, onProductClick }) {
  return <div onClick={() => onProductClick(product.id)}>{product.name}</div>;
});

function ProductList({ products }) {
  const handleProductClick = useCallback((id) => {
    console.log('Product clicked:', id);
  }, []);
  
  return (
    <div>
      {products.map(product => (
        <ProductCard 
          key={product.id} 
          product={product} 
          onProductClick={handleProductClick}
        />
      ))}
    </div>
  );
}


Optimizing Context API Usage

Using one large React Context for all states can lead to unneeded re-renders, which hampers performance. Instead, break the context into smaller segments based on domain and how often they change. For example, having separate contexts for user data, theme, and cart state ensures that only the components that rely on a specific piece of data will re-render. Additionally, employing custom hooks for state management can keep your code modular and easier to maintain.


// Problematic: Single large context causing frequent re-renders

const AppContext = createContext();

function AppProvider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const [cart, setCart] = useState([]);
  
  const value = { user, setUser, theme, setTheme, cart, setCart };
  
  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}


// Better: Split contexts by domain and update frequency

const UserContext = createContext();
const ThemeContext = createContext();
const CartContext = createContext();

function AppProviders({ children }) {
  return (
    <UserContext.Provider value={useUserState()}>
      <ThemeContext.Provider value={useThemeState()}>
        <CartContext.Provider value={useCartState()}>
          {children}
        </CartContext.Provider>
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}


2. Data Fetching Optimizations


Implementing SWR or React Query

In Next.js, using useEffect may lead to repeated requests. To streamline data fetching, SWR and React Query offer robust solutions through caching, revalidation, and error management. SWR employs a "stale-while-revalidate" strategy to provide immediate cached data while updating it in the background, whereas React Query offers extra capabilities like query invalidation and pagination, enhancing both performance and the overall user experience.


// Naïve approach: Fetching on each component mount

function ProductDetail({ productId }) {
  const [product, setProduct] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    async function fetchProduct() {
      setLoading(true);
      const res = await fetch(`/api/products/${productId}`);
      const data = await res.json();
      setProduct(data);
      setLoading(false);
    }
    
    fetchProduct();
  }, [productId]);
  
  if (loading) return <p>Loading...</p>;
  
  return <div>{product.name}</div>;
}


//Better: Using SWR for caching, revalidation, and error handling

import useSWR from 'swr';

function ProductDetail({ productId }) {
  const { data: product, error, isLoading } = useSWR(
    `/api/products/${productId}`,
    fetcher
  );
  
  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error loading product</p>;
  
  return <div>{product.name}</div>;
}


Leveraging getStaticProps and getServerSideProps

In Next.js, getServerSideProps retrieves data on every request, making it perfect for dynamic content like news. On the other hand, getStaticProps runs during the build process, boosting performance for static pages. Incremental Static Regeneration (ISR) enables periodic updates without rebuilding the entire site, striking a balance between freshness and efficiency.


// For frequently changing data that needs SEO

export async function getServerSideProps() {
  const res = await fetch('https://api.example.com/news');
  const news = await res.json();
  
  return { props: { news } };
}


// For relatively static data with occasional updates

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/products');
  const products = await res.json();
  
  return { 
    props: { products },
    // Re-generate at most once every hour
    revalidate: 3600
  };
}


3. Image and Asset Optimization


The Next.js <Image> component boosts performance by lazy loading and adjusting to different screen sizes. It's best to avoid using layout="fill" without a proper parent container to prevent unexpected layout shifts. Instead, set explicit width and height for consistency and use placeholder="blur" to enhance the user experience. Also, update your next.config.js to support modern image formats like AVIF and WebP for optimal performance.


// Anti-pattern: Not specifying proper dimensions

<Image src="/product.jpg" alt="Product" layout="fill" />

// Better: Specify explicit dimensions and use proper layout
<Image 
  src="/product.jpg" 
  alt="Product" 
  width={300} 
  height={200} 
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,..."
/>


For maximum performance, configure your next.config.js to optimize images:

module.exports = {
  images: {
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    domains: ['cdn.yoursite.com'],
    formats: ['image/avif', 'image/webp'],
  },
};


4. Route and Bundle Optimization


Dynamic Imports for Code Splitting

Dynamic imports in Next.js help minimize the initial JavaScript load by only bringing in components when they're actually needed. This is particularly beneficial for heavy libraries such as charts or maps. With next/dynamic, you can lazy-load components with a fallback UI, which enhances both performance and user experience. For components that only run on the client side, it's advisable to disable server-side rendering (ssr: false) to ensure they load efficiently without impacting the server-rendered content


//Importing everything upfront
import { Chart } from 'heavy-chart-library';

//Dynamic import when needed
import dynamic from 'next/dynamic';

const Chart = dynamic(() => import('heavy-chart-library').then(mod => mod.Chart), {
  loading: () => <p>Loading chart...</p>,
  ssr: false // Disable SSR for components that don't support it
});


Route-Based Code Splitting

Next.js automatically divides code by route, ensuring that only the necessary parts load for the current page. You can further enhance this by prefetching routes that users might visit next. For example, triggering router.prefetch(href) on a mouse enter event lets Next.js load the page in the background, leading to faster navigation and an improved user experience.


// Pre-fetch routes that are likely to be visited

import { useRouter } from 'next/router';

function NavLink({ href, children }) {
  const router = useRouter();
  
  return (
    <a 
      href={href}
      onClick={(e) => {
        e.preventDefault();
        router.push(href);
      }}
      onMouseEnter={() => router.prefetch(href)}
    >
      {children}
    </a>
  );
}


Conclusion

The key to optimizing Next.js performance depends on making strategic decisions between code and infrastructure elements. This article examines how rendering methods such as SSR, SSG, ISR and CSR affect performance. The article then explores essential optimization techniques which include stopping unnecessary re-renders and enhancing data fetching through SWR or React Query as well as image optimization and dynamic imports and route-based code splitting.


The implementation of these best practices leads to the development of fast and efficient Next.js applications which provide users with a seamless experience. The collective effect of minor performance enhancements leads to better scalability and performance of web applications through time reduction and improved responsiveness and navigation smoothness.



Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks