0

Save bill and improve performance on nextjs app

Learn how to optimize your Next.js app with a Smart Link component that reduces prefetching overhead, lowers server costs, and ensures fast navigation.

Save bill and improve performance on nextjs app

Next.js is a powerful framework for building fast, scalable web applications, but its default behavior can sometimes lead to performance bottlenecks. One such issue arises with the Link component, which prefetches every link in the viewport by default. While prefetching is great for speeding up navigation, it can overwhelm your server with requests, increase hosting costs, and force users to download unnecessary data. For applications with heavy traffic, this can significantly slow down performance.

So, how can we optimize this behavior without compromising the user experience? Completely disabling prefetching is not ideal, as it increases page load times. Instead, we can create a Smart Link component that intelligently manages prefetching to balance performance and speed.

The Problem with Default Prefetching

The Link component in Next.js automatically prefetches linked pages when they enter the viewport. This means that if a page has multiple links visible at once, every single one will trigger a prefetch request. For example, a blog page with 20 article links could send 20 simultaneous requests to your server, spiking resource usage and potentially slowing down the app. Additionally, users on mobile networks may end up downloading data for pages they never visit, which is inefficient.

Disabling prefetching entirely (by setting prefetch={false}) avoids these requests but sacrifices the snappy navigation that users expect from Next.js apps. We need a solution that retains the benefits of prefetching while minimizing its downsides.

Smarter Solution

The idea is to create a wrapper component around Link that disables prefetching by default and only enables it when the user interacts with the link. Specifically, we can trigger prefetching on hover or focus events for desktop users. This ensures that prefetching only happens for links the user is likely to click, reducing unnecessary requests.

Here's how it works:

  • Wrap the Link component in a custom SmartLink component.
  • Disable prefetching by default using prefetch={false}.
  • Use event handlers (onMouseEnter and onFocus) to dynamically prefetch the link when the user hovers over or focuses on it.

This approach mimics "on-hover prefetching" which is more efficient than prefetching every link in the viewport.

Handling Touch Devices

On touch devices, there's no hover event, so prefetching would only trigger when the user taps the link (e.g., via onClick). This can be too late, as the prefetching process needs time to complete. Using onTouchStart might seem like a solution, but it doesn't always work reliably in practice due to browser inconsistencies.

To address this, we can make the SmartLink component even smarter by detecting whether the user is on a touch device. For touch devices, we can enable viewport-based prefetching selectively, as touch screens (example, mobile phones) typically display fewer links in the viewport compared to desktop screens. For example, a mobile sidebar menu might only show a handful of links at a time, making viewport prefetching less resource-intensive.

To detect touch devices, we can check for the presence of touch events or use media queries. Here's a basic approach:

  • Use JavaScript to check if the device supports touch events ("ontouchstart" in window).
  • Alternatively, use a library like react-device-detect for more robust detection.
  • If a touch device is detected, enable prefetching for links in the viewport, but limit it to critical links (e.g., sidebar navigation or primary calls-to-action).

Implementation

Below is a sample implementation of the SmartLink component in NextJS:

example.tsx
import Link from "next/link";
import { useState } from "react";
 
export function SmartLink(props: React.ComponentProps<typeof Link>) {
  const [prefetch, setPrefetch] = useState(false);
  const fire = () => setPrefetch(true);
 
  return (
    <Link
      {...props}
      prefetch={prefetch}
      onMouseEnter={fire}
      onFocus={fire}
      onTouchStart={fire}
    />
  );
}

How It Works

  • Prefetch State Management: The SmartLink component uses a useState hook to manage a prefetch boolean, initialized to false. This disables prefetching by default when the link is rendered.
  • Event-Driven Prefetching: The component listens for three events: onMouseEnter (hover), onFocus (keyboard navigation or focus), and onTouchStart (touch interaction). When any of these events occur, the fire function is called, setting prefetch to true.
  • Dynamic Prefetch Activation: The prefetch prop on the Link component is tied to the prefetch state. Once the state becomes true, Next.js triggers prefetching for the link’s destination, ensuring the page is ready for navigation.
  • Touch Device Support: The onTouchStart event ensures that prefetching starts as soon as the user touches the link on a touch device. While not as early as viewport prefetching, this approach still reduces unnecessary requests compared to prefetching all links in the viewport.
  • Props Passthrough: The component spreads all props (...props) to the underlying Link component, ensuring compatibility with any additional props (e.g., className, onClick) passed to SmartLink.

Usage

Replace the standard Link component with SmartLink in your Next.js app:

example.jsx
import { SmartLink } from "@/link";
 
export function Sidebar() {
  return (
    <div>
      <div>Navigation</div>
      {Array.from({ length: 10 }, (_, i) => (
        <SmartLink key={i} href={`/page/${i}`}>
          Page {i}
        </SmartLink>
      ))}
    </div>
  );
}
  • Reduced Server Load: By prefetching only when the user is likely to click a link, you significantly cut down on unnecessary server requests.
  • Lower Costs: Fewer requests mean lower bandwidth and hosting costs, especially for high-traffic apps.
  • Improved User Experience: Users on mobile networks download less unnecessary data, and page navigation remains fast.
  • Scalability: The solution scales well for apps with many links, as it avoids the performance hit of prefetching everything in the viewport.

Try It Yourself

The beauty of this approach is its flexibility. You can customize the SmartLink component to suit your app's specific needs. Experiment with touch device detection, prefetching triggers, and viewport optimizations to find the perfect balance for your use case.

Have you built a similar solution or found other ways to optimize Next.js performance? Share your ideas and implementations online let's make the web faster together!