Disclaimer Next.js is a dynamically developed framework and a lot of old articles about it could be outdated because Next.js made a huge leap forward. I wrote this article in May 2021 and now the current version is 10.2.3 I suggest checking while you look at examples because some tips could be outdated due to improved API docs Features Were Released You Probably Don't Know If you use Next.js for a long time your projects should have a huge amount of code, and you just keep writing it in the style that it already has. Sometimes because you use to and sometimes because you use your codebase as a catalog of snippets and examples. That's why you can skip new cool stuff. Server redirects and not found status These features, as well as previous ones, were released in version 10 and I still see a lot of questions about how to make redirect on the server. First of all, I suggest checking if you use modern fetching methods because only they get updates and new features. I explained other reasons why to use modern fetching methods. here Here is a simple way to make redirect or show page 404: { { : { : , : , } } } { { : } } // Redirect export ( ) function getServerSideProps return redirect destination '/' permanent false // Page 404 export ( ) function getServerSideProps return notFound true Automatic resolving of href param Try to find in your project that has and as prop as well. If you found it, it's time to update that links. Because starts with 10.x version you don't need to pass both of the params you can drop param and rename to , and it will work well. next/link href href as href Link { } { } import from 'next/link' // Before ( ) function MyComponent return Blog post < = = > Link href "/posts/[post]" as "/posts/blog-post" </ > Link // Since v10.x ( ) function MyComponent return Blog post < = > Link href "/posts/blog-post" </ > Link Client Router Functions I noticed that people often use from . If you use it in functions that were wrapped in useCallback or just inside useEffect you have to mention it in dependencies like in the example down below. router.push useRouter { useEffect, useCallback } { useRouter } { { push } = useRouter() handleClick = useCallback( { push( ) }, [push]) useEffect( { (...) { push( ) } }, [push]) import from 'react' import from 'next/router' ( ) function MyComponent const const => () // some logic '/profile' => () if '/sign-in' return ... } <> </> It's completely unnecessary because these functions don't update over re-renders but ESlint rules will tell you that you should do it. Solutions for this are fairly easy. Just use routing functions from the Router. It makes your code cleaner and nicer 😉 Router { handleClick = useCallback( { Router.push( ) }, []) useEffect( { (...) { Router.push( ) } }, []) import from 'next/router' ( ) function MyComponent const => () // some logic '/profile' => () if '/sign-in' return ... } <> </> Sometimes when I work with the router I need to get the current pathname. But react/router stores the dynamic representation of the router in that field, and it's the same as router filed. We can create these params by ourselves with . I created a wrapper over and always have access to the props If I need them. asPath useRouter { useRouter useNextRouter } { router = useNextRouter() [pathname, queryString = ] = router.asPath.split( ); .assign(router, { pathname, queryString }); } // lib/router.ts import as from 'next/router' export ( ) function useRouter const const "" "?" return Object The wrapper gives us handy props that we can use in a situation when we need to update a current route. Router { useRouter } { { query, pathname } = useRouter() { params = FormData(evt.currentTarget).getAll( ); evt.preventDefault(); Router.replace( { pathname, : { ...query, params }}, , { : } ) } ( <button type="submit">Apply</button> ) } import from 'next/router' import from 'lib/router' ( ) function MyComponent const ( ) function handleSubmit evt const new "fruits" query null shallow true return {...} < > form </ > form Do you wonder why the second param in is ? Since 10.x with automatic resolving, we can skip that param. Router.replace null Since we have such a feature as automatic resolving we can use it , but it is a small inconvenience in its API. Because if you want to pass you should pass dynamic route to the first argument and full path to the second in earlier versions and Next.js kept those 3 arguments for backward compatibility. I have small handy functions that help me to use router functions easier. next/router options NextRouter { NextRouter.push(url, , opts) } Router = { ...NextRouter, push, } Router Router, { useRouter } { { query } = useRouter() { params = FormData(evt.currentTarget).getAll( ); evt.preventDefault(); Router.push({ pathname, : { ...query, params } }, { : }); } // lib/router.ts import from 'next/router' ( ) function push url, opts return null const export default /* ... */ // pages/params.tsx import from 'lib/router' ( ) function MyComponent const ( ) function handleSubmit evt const new "fruits" query shallow true return {...} } <> </> Conclusion Thank you for reading the article till the end. You can find complete code on . There is always some way to make good tools better. Small improvements could help you and your project a lot. GitHub Previously published at https://pavel.mineev.me/blog/nextjs-router-tips-and-tricks