Shallow routing with Next.js v13 App directory
With the recent release of the stable version of the Next.js App Directory, I tried to construct a full-fledged website using the latest methods and approaches. However, I encountered a challenge.
The Challenge
The issue arose when I attempted to apply multiple filters without reloading the page. Initially, the solution seemed straightforward — using the shallow routing method from useRouter. However, to my surprise, the latest version of Next.js no longer supports this API. (There is a feature called “soft navigation” as well which didn’t work for some reason)
In Next.js v13, adding or removing search parameters invariably triggers a hard-reload. In contrast, Next.js v12 allowed for a command like router.push('/?counter=10', undefined, { shallow: true })
to be executed without initiating a hard reload. Unfortunately, in the current version, similar code always results in a hard-reload, causing all components to re-render, even though it’s the same page.
Solution
When faced with such limitations in newly implemented applications, it’s always a good idea to revisit the web APIs. Our goal here is to find a way to modify the URL instantly without triggering a full page reload.
A popular method to achieve this is by using “window.history.pushState()”. Here’s how you can use it:
window.history.pushState({}, "", window.location.pathname + "?" + params.toString())
read more about pushState here.
now I needed to listen to the changes in the URL from another components. So let’s write a hook to do that for us.
import { useEffect } from 'react';
export const usePushStateListener = (callback) => {
useEffect(() => {
// make a copy of original function to avoid complications
const originalPushState = history.pushState;
history.pushState = function (data, title, url) {
originalPushState.apply(history, [data, title, url]);
callback(url);
return () => {
history.pushState = originalPushState; // restore the copy
};
}, [callback]);
};
When the “window.history.pushState” method is called in any components, the overridden “pushState” method defined in the “usePushStateListener” hook will be executed. This allows the hook to listen the URL changes and trigger callback function with the updated URL.
Finally import the hook to the components that need to know about URL changes: (in my case was a fetch call)
usePushStateListener(refetch);
Reminder: This is all experimental and we are waiting for next to implement the write API to achieve this functionality. So please use it with caution.