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 customSmartLink
component. - Disable prefetching by default using
prefetch={false}
. - Use event handlers (
onMouseEnter
andonFocus
) 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:
How It Works
- Prefetch State Management: The
SmartLink
component uses auseState
hook to manage aprefetch
boolean, initialized tofalse
. 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), andonTouchStart
(touch interaction). When any of these events occur, thefire
function is called, settingprefetch
totrue
. - Dynamic Prefetch Activation: The
prefetch
prop on theLink
component is tied to theprefetch
state. Once the state becomestrue
, 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 underlyingLink
component, ensuring compatibility with any additional props (e.g.,className
,onClick
) passed toSmartLink
.
Usage
Replace the standard Link
component with SmartLink
in your Next.js app:
Benefits of the Smart Link Component
- 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!