next.js
next.js copied to clipboard
Docs: How to migrate from async router.push to new `next/navigation` push method
What is the improvement or update you wish to see?
The new router in next/navigation no longer returns a promise from the push method. From what I've been able to understand, this is so that it can work with the startTransition feature of React, but the Next docs don't contain any information about this or how to successfully replace any functionality that was relying on "awaiting" a router.push call
Is there any context that might help us understand?
The docs for the push method are currently one sentence here: https://nextjs.org/docs/app/api-reference/functions/use-router#userouter
The migration guide does not mention the fact that the method is no longer async or describe how a migration should take place to the new push
https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#step-5-migrating-routing-hooks
This is the one place I've been able to find mention of how it can be used with useTransition, although it also doesn't seem to trigger the pending state in many cases (maybe it just doesn't work in page router?) https://github.com/vercel/next.js/discussions/49810
Does the docs page already exist? Please link to it.
https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#step-5-migrating-routing-hooks
Here here, also need it badly.
Agreed. We built our own hacky solution to await router pushes, but would really like built-in support for this!
Agreed. We built our own hacky solution to await router pushes, but would really like built-in support for this!
Can you please share your workaround
Agreed. We built our own hacky solution to await router pushes, but would really like built-in support for this!
Can you please share your workaround
// modify router so that it awaits the route change before returning
export const useAsyncRouter = () => {
const router = useRouter();
const pathname = usePathname();
const [resolveRouteChanged, setResolveRouteChanged] = useState<(() => void) | null>(null);
useEffect(() => {
resolveRouteChanged?.();
}, [pathname]);
const asyncRouter = useMemo(() => {
const push = async <RouteType>(href: NextRouteTypes.RouteImpl<RouteType>) => {
router.push(href as any);
await new Promise<void>((resolve) => {
// wait for the route change to complete
setResolveRouteChanged(resolve);
});
};
return { ...router, push };
}, [router]);
return asyncRouter;
};
Here are an alternative implementation of https://stackoverflow.com/a/77931487/6638583 workaround code
'use client'
import { useRouter } from 'next/navigation'
import { useEffect, useTransition } from 'react'
// Define the type for the observer callback function
type ObserverCallback = () => void
const createRouteObserver = () => {
let observer: ObserverCallback | null = null
const setObserver = (callback: ObserverCallback) => {
observer = callback
}
const notify = () => {
if (observer) {
observer()
}
}
return { setObserver, notify }
}
const routeObserver = createRouteObserver()
export const useAsyncRoutePush = () => {
const [isPending, startTransition] = useTransition()
const router = useRouter()
const asynPush = async (path: string) => {
return new Promise<void>((resolve) => {
startTransition(() => {
router.push(path)
})
routeObserver.setObserver(() => {
resolve()
})
})
}
useEffect(() => {
if (!isPending) {
routeObserver.notify()
}
}, [isPending])
return asynPush
}
Usage:
export default function MyComponent() {
const asyncPush = useAsyncRoutePush()
return <Button onClick={async () => {
await asyncPush('/')
console.log("Route changed")
}}>
Go / route
</Button>
}
This should be mentioned in the documentation as a breaking change as there's no built-in support for this feature now.
This feature is really needed because I have an MUI grid which I added custom url tracking of the page in the url via push and when user clicks so fast since it's async all of it that means the router.push is not being awaited and clicking too fast is causing a crash look below
+1 This feature is really needed. It's been a year with no response? I don't know why the Next.js team prioritizes RSCs and server stuff while neglecting important features like these.
Still nothing? Is something what is keeping it back from implementing this in app router?
I would love it implemented too, it's a bummer that we have to do it manually
Same here, not enjoying writing hacks to get around the fact that router.push isn't async