iTranslated by AI
A custom hook to enable seamless prefetching with router.push() in Next.js
router.push() does not prefetch automatically
When performing client-side routing (CSR) between routes in Next.js, the first thing you likely use is the Link component provided by next/link.
In this case, when the corresponding component enters the viewport, it automatically prefetches the link destination, ensuring a smooth page transition.
Reference: https://nextjs.org/docs/api-reference/next/link
However, there are times when you might use useRouter provided by next/router to navigate via router.push() instead of using the Link component for CSR.
To prefetch with this method, you need to call router.prefetch() manually.
I thought it would be great if this could be done as easily as with the Link component, so I encapsulated a series of logic into a custom hook.
Best suited for people who:
- Want to apply prefetching to router.push() too!
- Hate writing logic using IntersectionObserver every single time.
- Use pathpida and don't want to go through the trouble of converting values to strings just for prefetching.
Additionally, it supports immediate prefetching instead of waiting for an element to enter the viewport, so please feel free to use it as a handy router hook.
How to use
First, please set up a Next.js project using create-next-app or a similar tool.
Then, install the library.
yarn add @dc7290/next-router-prefetch
Here is the interface for this custom hook.
useRouterPrefetch(url, observe, nextRouterOptions);
And here is an example of actual usage in a component that executes router.push().
import React from "react";
import { useRouterPrefetch } from '@dc7290/next-router-prefetch';
const FooComponent: React.VFC = () => {
const {
handleRouterPush,
prefetchTarget,
} = useRouterPrefetch<HTMLDivElement>('/foo');
return (
<div ref={prefetchTarget} onClick={handleRouterPush}>
Link to 'foo' page.
</div>
);
};
(For JavaScript usage examples, click here)
Now, when the div tag enters the viewport, it prefetches the link destination, and when clicked, the page transitions at high speed (depending on the logic of the destination page, of course lol).
Here is also how to use it for immediate prefetching without using IntersectionObserver.
import React, { useEffect } from "react";
import { useRouterPrefetch } from '@dc7290/next-router-prefetch';
const FooComponent: React.VFC = () => {
const { handleRouterPush } = useRouterPrefetch("/bar", false);
useEffect(() => {
if (login) {
handleRouterPush();
}
});
return <div>Example login page.</div>;
};
Now, let me provide a detailed explanation.
Arguments
First Argument
The first argument is the destination URL.
Since this uses the same format as router.push, you can provide either a string or a UrlObject.
Normally, router.prefetch only accepts strings, but this hook internally converts any UrlObject into a string so that it works seamlessly. There's no need to manually convert URLs to strings for prefetching.
This feature makes it exceptionally easy to use alongside pathpida!
Since pathpida's pagesPath.$url() handles hashes and queries and returns a UrlObject, you can simply pass that as the first argument to handle both navigation and prefetching at the same time.
Second Argument
The second argument is a boolean value indicating whether to use IntersectionObserver for prefetching.
The default is true.
If you pass false, the link destination will be prefetched immediately after rendering.
Third Argument
This is an object containing options similar to the default options of router.push, with the following keys:
| Key | Value | Description |
|---|---|---|
| as | string or UrlObject | An optional decorator for the URL that will be shown in the browser's address bar. Before Next.js 9.5.3, this decorator was used for dynamic routes; see the previous documentation for how that worked. |
| options | object | An optional object with the following configuration options: scroll: Whether to scroll to the top of the page after navigation. Defaults to true. shallow: Update the path of the current page without rerunning getStaticProps, getServerSideProps, or getInitialProps. Defaults to false. locale: The active locale is automatically prepended. A different locale can be specified. If false, href must include the locale and the default behavior is disabled. |
Return Value
If the second argument (observe) is true, it returns handleRouterPush and prefetchTarget.
If false, it returns handleRouterPush.
handleRouterPush is, as the name suggests, a function that navigates to the provided link destination.
You can use it for events you want to trigger or within useEffect, etc.
prefetchTarget is a ref object configured to be observed by IntersectionObserver.
Set this to the ref of the element you want to prefetch when it enters the viewport.
Browser Support
As you may know, IntersectionObserver does not work in IE...
If you need to support IE, you can insert:
via a script tag, or refer to:
for more details.
Future Improvement Plans
Currently, I am considering an improvement proposal to allow passing IntersectionObserver options.
As a use case, I think there might be a need to prefetch slightly before an element enters the viewport.
To achieve that, I believe it would be better to just allow passing the IntersectionObserver options directly.
Aside
Developing a library for the first time has hopefully improved my engineering skills a bit!
Above all, I was simply happy to be developing a library, which I used to think was way out of my league lol.
Also, there's something I feel I messed up this time: because I didn't fully understand semantic versioning, the version has already jumped to 2 even though it's still in the early stages...
However, I'll take it as a good opportunity since I learned how the libraries I always use manage their versioning!
References
Discussion