跳到主要内容

useRouter

如果你想在应用的任何函数组件中访问 router 对象,可以使用 useRouter hook,看看以下示例:

import { useRouter } from 'next/router'

function ActiveLink({ children, href }) {
const router = useRouter()
const style = {
marginRight: 10,
color: router.asPath === href ? 'red' : 'black',
}

const handleClick = (e) => {
e.preventDefault()
router.push(href)
}

return (
<a href={href} onClick={handleClick} style={style}>
{children}
</a>
)
}

export default ActiveLink

useRouter 是一个 React Hook,这意味着它不能与类一起使用。你可以使用 withRouter 或将你的类包装在函数组件中。

router 对象

以下是 useRouterwithRouter 返回的 router 对象的定义:

  • pathnameString - /pages 后当前路由文件的路径。因此,不包括 basePathlocale 和尾部斜杠(trailingSlash: true)。
  • queryObject - 解析为对象的查询字符串,包括动态路由参数。如果页面不使用服务端渲染,则在预渲染期间它将是一个空对象。默认为 {}
  • asPathString - 浏览器中显示的路径,包括搜索参数并遵循 trailingSlash 配置。不包括 basePathlocale
  • isFallbackboolean - 当前页面是否处于回退模式
  • basePathString - 活动的 basePath(如果已启用)。
  • localeString - 活动区域设置(如果已启用)。
  • localesString[] - 所有支持的区域设置(如果已启用)。
  • defaultLocaleString - 当前默认区域设置(如果已启用)。
  • domainLocalesArray<{domain, defaultLocale, locales}> - 任何配置的域区域设置。
  • isReadyboolean - 路由器字段是否在客户端更新并准备好使用。应该只在 useEffect 方法内使用,而不用于在服务器上有条件地渲染。有关自动静态优化页面用例的相关文档,请参阅相关文档
  • isPreviewboolean - 应用当前是否处于预览模式

如果页面使用服务端渲染或自动静态优化渲染,使用 asPath 字段可能会导致客户端和服务器之间不匹配。避免使用 asPath,直到 isReady 字段为 true

router 内包含以下方法:

router.push

处理客户端转换,此方法对于 next/link 不够用的情况很有用。

router.push(url, as, options)
  • urlUrlObject | String - 要导航到的 URL(有关 UrlObject 属性,请参阅 Node.JS URL 模块文档)。
  • asUrlObject | String - 可选的装饰器,用于浏览器 URL 栏中显示的路径。在 Next.js 9.5.3 之前,这用于动态路由。
  • options - 可选对象,具有以下配置选项:
    • scroll - 可选布尔值,控制导航后是否滚动到页面顶部。默认为 true
    • shallow:更新当前页面的路径,而不重新运行 getStaticPropsgetServerSidePropsgetInitialProps。默认为 false
    • locale - 可选字符串,指示新页面的区域设置

对于外部 URL,你不需要使用 router.pushwindow.location 更适合这些情况。

导航到 pages/about.js,这是一个预定义的路由:

import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

return (
<button type="button" onClick={() => router.push('/about')}>
点击我
</button>
)
}

导航到 pages/post/[pid].js,这是一个动态路由:

import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

return (
<button type="button" onClick={() => router.push('/post/abc')}>
点击我
</button>
)
}

将用户重定向到 pages/login.js,对于受身份验证保护的页面很有用:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

// 在这里你会获取并返回用户
const useUser = () => ({ user: null, loading: false })

export default function Page() {
const { user, loading } = useUser()
const router = useRouter()

useEffect(() => {
if (!(user || loading)) {
router.push('/login')
}
}, [user, loading])

return <p>重定向中...</p>
}

导航后重置状态

在 Next.js 中导航到同一页面时,页面的状态不会默认重置,因为除非父组件发生变化,否则 React 不会卸载。

pages/[slug].js
import Link from 'next/link'
import { useState } from 'react'
import { useRouter } from 'next/router'

export default function Page(props) {
const router = useRouter()
const [count, setCount] = useState(0)
return (
<div>
<h1>Page: {router.query.slug}</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>增加计数</button>
<Link href="/one">one</Link> <Link href="/two">two</Link>
</div>
)
}

在上面的示例中,在 /one/two 之间导航不会重置计数。useState 在渲染之间保持,因为顶层 React 组件 Page 是相同的。

如果你不想要这种行为,有几个选项:

  • 使用 useEffect 手动确保更新每个状态。在上面的示例中,可能看起来像这样:

    useEffect(() => {
    setCount(0)
    }, [router.query.slug])
  • 使用 React key告诉 React 重新挂载组件。要对所有页面执行此操作,可以使用自定义 app:

    pages/_app.js
    import { useRouter } from 'next/router'

    export default function MyApp({ Component, pageProps }) {
    const router = useRouter()
    return <Component key={router.asPath} {...pageProps} />
    }

使用 URL 对象

你可以以与 [next/link](/nextjs/pages/api-reference/components/link#传递 URL 对象) 相同的方式使用 URL 对象。适用于 urlas 参数:

import { useRouter } from 'next/router'

export default function ReadMore({ post }) {
const router = useRouter()

return (
<button
type="button"
onClick={() => {
router.push({
pathname: '/post/[pid]',
query: { pid: post.id },
})
}}
>
点击这里阅读更多
</button>
)
}

router.replace

类似于 next/link 中的 replace prop,router.replace 将阻止向 history 堆栈添加新的 URL 条目。

router.replace(url, as, options)
  • router.replace 的 API 与 router.push 的 API 完全相同。

Take a look at the following example:

import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

return (
<button type="button" onClick={() => router.replace('/home')}>
Click me
</button>
)
}

router.prefetch

Prefetch pages for faster client-side transitions. This method is only useful for navigations without next/link, as next/link takes care of prefetching pages automatically.

This is a production only feature. Next.js doesn't prefetch pages in development.

router.prefetch(url, as, options)
  • url - The URL to prefetch, including explicit routes (e.g. /dashboard) and dynamic routes (e.g. /product/[id])
  • as - Optional decorator for url. Before Next.js 9.5.3 this was used to prefetch dynamic routes.
  • options - Optional object with the following allowed fields:
    • locale - allows providing a different locale from the active one. If false, url has to include the locale as the active locale won't be used.

Let's say you have a login page, and after a login, you redirect the user to the dashboard. For that case, we can prefetch the dashboard to make a faster transition, like in the following example:

import { useCallback, useEffect } from 'react'
import { useRouter } from 'next/router'

export default function Login() {
const router = useRouter()
const handleSubmit = useCallback((e) => {
e.preventDefault()

fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
/* Form data */
}),
}).then((res) => {
// Do a fast client-side transition to the already prefetched dashboard page
if (res.ok) router.push('/dashboard')
})
}, [])

useEffect(() => {
// Prefetch the dashboard page
router.prefetch('/dashboard')
}, [router])

return (
<form onSubmit={handleSubmit}>
{/* Form fields */}
<button type="submit">Login</button>
</form>
)
}

router.beforePopState

In some cases (for example, if using a Custom Server), you may wish to listen to popstate and do something before the router acts on it.

router.beforePopState(cb)
  • cb - The function to run on incoming popstate events. The function receives the state of the event as an object with the following props:
    • url: String - the route for the new state. This is usually the name of a page
    • as: String - the url that will be shown in the browser
    • options: Object - Additional options sent by router.push

If cb returns false, the Next.js router will not handle popstate, and you'll be responsible for handling it in that case. See Disabling file-system routing.

You could use beforePopState to manipulate the request, or force a SSR refresh, as in the following example:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

useEffect(() => {
router.beforePopState(({ url, as, options }) => {
// I only want to allow these two routes!
if (as !== '/' && as !== '/other') {
// Have SSR render bad routes as a 404.
window.location.href = as
return false
}

return true
})
}, [router])

return <p>Welcome to the page</p>
}

router.back

Navigate back in history. Equivalent to clicking the browser’s back button. It executes window.history.back().

import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

return (
<button type="button" onClick={() => router.back()}>
Click here to go back
</button>
)
}

router.reload

Reload the current URL. Equivalent to clicking the browser’s refresh button. It executes window.location.reload().

import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

return (
<button type="button" onClick={() => router.reload()}>
Click here to reload
</button>
)
}

router.events

You can listen to different events happening inside the Next.js Router. Here's a list of supported events:

  • routeChangeStart(url, { shallow }) - Fires when a route starts to change
  • routeChangeComplete(url, { shallow }) - Fires when a route changed completely
  • routeChangeError(err, url, { shallow }) - Fires when there's an error when changing routes, or a route load is cancelled
    • err.cancelled - Indicates if the navigation was cancelled
  • beforeHistoryChange(url, { shallow }) - Fires before changing the browser's history
  • hashChangeStart(url, { shallow }) - Fires when the hash will change but not the page
  • hashChangeComplete(url, { shallow }) - Fires when the hash has changed but not the page

Good to know: Here url is the URL shown in the browser, including the basePath.

For example, to listen to the router event routeChangeStart, open or create pages/_app.js and subscribe to the event, like so:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function MyApp({ Component, pageProps }) {
const router = useRouter()

useEffect(() => {
const handleRouteChange = (url, { shallow }) => {
console.log(
`App is changing to ${url} ${
shallow ? 'with' : 'without'
} shallow routing`
)
}

router.events.on('routeChangeStart', handleRouteChange)

// If the component is unmounted, unsubscribe
// from the event with the `off` method:
return () => {
router.events.off('routeChangeStart', handleRouteChange)
}
}, [router])

return <Component {...pageProps} />
}

We use a Custom App (pages/_app.js) for this example to subscribe to the event because it's not unmounted on page navigations, but you can subscribe to router events on any component in your application.

Router events should be registered when a component mounts (useEffect or componentDidMount / componentWillUnmount) or imperatively when an event happens.

If a route load is cancelled (for example, by clicking two links rapidly in succession), routeChangeError will fire. And the passed err will contain a cancelled property set to true, as in the following example:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function MyApp({ Component, pageProps }) {
const router = useRouter()

useEffect(() => {
const handleRouteChangeError = (err, url) => {
if (err.cancelled) {
console.log(`Route to ${url} was cancelled!`)
}
}

router.events.on('routeChangeError', handleRouteChangeError)

// If the component is unmounted, unsubscribe
// from the event with the `off` method:
return () => {
router.events.off('routeChangeError', handleRouteChangeError)
}
}, [router])

return <Component {...pageProps} />
}

The next/compat/router export

This is the same useRouter hook, but can be used in both app and pages directories.

It differs from next/router in that it does not throw an error when the pages router is not mounted, and instead has a return type of NextRouter | null. This allows developers to convert components to support running in both app and pages as they transition to the app router.

A component that previously looked like this:

import { useRouter } from 'next/router'
const MyComponent = () => {
const { isReady, query } = useRouter()
// ...
}

Will error when converted over to next/compat/router, as null can not be destructured. Instead, developers will be able to take advantage of new hooks:

import { useEffect } from 'react'
import { useRouter } from 'next/compat/router'
import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
const router = useRouter() // may be null or a NextRouter instance
const searchParams = useSearchParams()
useEffect(() => {
if (router && !router.isReady) {
return
}
// In `app/`, searchParams will be ready immediately with the values, in
// `pages/` it will be available after the router is ready.
const search = searchParams.get('search')
// ...
}, [router, searchParams])
// ...
}

This component will now work in both pages and app directories. When the component is no longer used in pages, you can remove the references to the compat router:

import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
const searchParams = useSearchParams()
// As this component is only used in `app/`, the compat router can be removed.
const search = searchParams.get('search')
// ...
}

Using useRouter outside of Next.js context in pages

Another specific use case is when rendering components outside of a Next.js application context, such as inside getServerSideProps on the pages directory. In this case, the compat router can be used to avoid errors:

import { renderToString } from 'react-dom/server'
import { useRouter } from 'next/compat/router'
const MyComponent = () => {
const router = useRouter() // may be null or a NextRouter instance
// ...
}
export async function getServerSideProps() {
const renderedComponent = renderToString(<MyComponent />)
return {
props: {
renderedComponent,
},
}
}

Potential ESLint errors

Certain methods accessible on the router object return a Promise. If you have the ESLint rule, no-floating-promises enabled, consider disabling it either globally, or for the affected line.

If your application needs this rule, you should either void the promise – or use an async function, await the Promise, then void the function call. This is not applicable when the method is called from inside an onClick handler.

The affected methods are:

  • router.push
  • router.replace
  • router.prefetch

Potential solutions

import { useEffect } from 'react'
import { useRouter } from 'next/router'

// Here you would fetch and return the user
const useUser = () => ({ user: null, loading: false })

export default function Page() {
const { user, loading } = useUser()
const router = useRouter()

useEffect(() => {
// disable the linting on the next line - This is the cleanest solution
// eslint-disable-next-line no-floating-promises
router.push('/login')

// void the Promise returned by router.push
if (!(user || loading)) {
void router.push('/login')
}
// or use an async function, await the Promise, then void the function call
async function handleRouteChange() {
if (!(user || loading)) {
await router.push('/login')
}
}
void handleRouteChange()
}, [user, loading])

return <p>Redirecting...</p>
}

withRouter

If useRouter is not the best fit for you, withRouter can also add the same router object to any component.

Usage

import { withRouter } from 'next/router'

function Page({ router }) {
return <p>{router.pathname}</p>
}

export default withRouter(Page)

TypeScript

To use class components with withRouter, the component needs to accept a router prop:

import React from 'react'
import { withRouter, NextRouter } from 'next/router'

interface WithRouterProps {
router: NextRouter
}

interface MyComponentProps extends WithRouterProps {}

class MyComponent extends React.Component<MyComponentProps> {
render() {
return <p>{this.props.router.pathname}</p>
}
}

export default withRouter(MyComponent)