useRouter

앱의 모든 함수 컴포넌트 내에서 router Object에 액세스하려면 useRouter 훅을 사용할 수 있습니다. 다음 예를 살펴보세요.

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

useRouterReact Hook (opens in a new tab)으로, 클래스와 함께 사용할 수 없습니다. withRouter를 사용하거나 클래스를 함수 컴포넌트로 감싸야 합니다.

router object

다음은 useRouterwithRouter에서 반환된 router 객체의 정의입니다:

  • pathname: String - /pages 이후의 현재 경로 파일입니다. 따라서 basePath, locale, 및 후행 슬래시(trailingSlash: true)는 포함되지 않습니다.
  • query: Object - 객체로 구문 분석된 쿼리 문자열입니다. dynamic route 매개변수를 포함합니다. 페이지가 Server-side Rendering을 사용하지 않는 경우 prerendering 중에는 빈 객체입니다. 기본값은 {}
  • asPath: String - 검색 매개변수를 포함하여 브라우저에 표시되는 경로이며, trailingSlash 설정을 존중합니다. basePathlocale은 포함되지 않습니다.
  • isFallback: boolean - 현재 페이지가 fallback mode인지 여부를 나타냅니다.
  • basePath: String - 활성화된 basePath (활성화된 경우).
  • locale: String - 활성화된 로케일 (활성화된 경우).
  • locales: String[] - 지원되는 모든 로케일 (활성화된 경우).
  • defaultLocale: String - 현재 기본 로케일 (활성화된 경우).
  • domainLocales: Array<{domain, defaultLocale, locales}> - 구성된 도메인 로케일.
  • isReady: boolean - 라우터 필드가 클라이언트 측에서 업데이트되어 사용 준비가 되었는지 여부. useEffect 메서드 내에서만 사용해야 하며 서버에서 조건부 렌더링을 위해 사용해서는 안 됩니다. automatically statically optimized pages와 관련된 사용 사례를 참조하세요.
  • isPreview: boolean - 애플리케이션이 현재 preview mode인지 여부.

asPath 필드를 사용하는 경우, 페이지가 서버 사이드 렌더링 또는 automatic static optimization를 사용하여 렌더링될 때 클라이언트와 서버 간의 불일치가 발생할 수 있습니다. isReady 필드가 true가 될 때까지 asPath 사용을 피하세요.

다음 메서드들은 router 객체에 포함됩니다:

router.push

클라이언트 측 전환을 처리합니다. 이 메서드는 next/link만으로는 충분하지 않은 경우에 유용합니다.

router.push(url, as, options)
  • url: UrlObject | String - 이동할 URL입니다 (UrlObject 속성에 대해서는 Node.JS URL 모듈 문서 (opens in a new tab)를 참조하세요).
  • as: UrlObject | String - 브라우저 URL 표시줄에 표시될 경로를 위한 선택적 데코레이터입니다. Next.js 9.5.3 이전에는 동적 라우트에 사용되었습니다.
  • options - 다음 구성 옵션이 있는 선택적 객체입니다:
    • scroll - 선택적 불리언 값으로, 이동 후 페이지 상단으로 스크롤할지 여부를 제어합니다. 기본값은 true입니다.
    • shallow: getStaticProps, getServerSideProps 또는 getInitialProps를 다시 실행하지 않고 현재 페이지의 경로를 업데이트합니다. 기본값은 false입니다.
    • locale - 선택적 문자열로, 새 페이지의 로케일을 나타냅니다.

외부 URL에는 router.push를 사용할 필요가 없습니다. 이런 경우에는 window.location (opens in a new tab)이 더 적합합니다.

미리 정의된 라우트인 pages/about.js로 이동하는 예시:

import { useRouter } from 'next/router'
 
export default function Page() {
  const router = useRouter()
 
  return (
    <button type="button" onClick={() => router.push('/about')}>
      Click me
    </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')}>
      Click me
    </button>
  )
}

사용자를 pages/login.js로 리다이렉트하는 예시입니다. 인증이 필요한 페이지에 유용합니다:

import { useEffect } from 'react'
import { useRouter } from 'next/router'
 
// 여기서 user를 가져와 반환합니다
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>Redirecting...</p>
}

Resetting state after navigation

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)}>Increase count</button>
      <Link href="/one">one</Link> <Link href="/two">two</Link>
    </div>
  )
}

위의 예시에서 /one/two 사이를 이동해도 count가 초기화되지 않습니다. 최상위 React 컴포넌트인 Page가 동일하기 때문에 useState는 렌더 간에 유지됩니다.

이러한 동작을 원하지 않는다면 몇 가지 옵션이 있습니다:

  • useEffect를 사용하여 각 상태를 수동으로 업데이트합니다. 위의 예시에서는 다음과 같이 구현할 수 있습니다:

    useEffect(() => {
      setCount(0)
    }, [router.query.slug])
  • React key를 사용하여 React에게 컴포넌트를 다시 마운트하도록 지시 (opens in a new tab)합니다. 모든 페이지에 이를 적용하려면 커스텀 앱을 사용할 수 있습니다:

    pages/_app.js
    import { useRouter } from 'next/router'
     
    export default function MyApp({ Component, pageProps }) {
      const router = useRouter()
      return <Component key={router.asPath} {...pageProps} />
    }

With URL object

next/link와 동일한 방식으로 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 },
        })
      }}
    >
      Click here to read more
    </button>
  )
}

router.replace

next/linkreplace 속성과 유사하게, router.replacehistory 스택에 새 URL 항목을 추가하지 않습니다.

router.replace(url, as, options)
  • router.replace의 API는 router.push의 API와 정확히 동일합니다.

다음 예시를 살펴보세요:

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

클라이언트 측 전환을 더 빠르게 하기 위해 페이지를 미리 가져옵니다. 이 메서드는 next/link를 사용하지 않는 내비게이션에만 유용합니다. next/link는 자동으로 페이지를 미리 가져오기 때문입니다.

이는 프로덕션 전용 기능입니다. Next.js는 개발 환경에서 페이지를 미리 가져오지 않습니다.

router.prefetch(url, as, options)
  • url - 미리 가져올 URL입니다. 명시적 라우트(예: /dashboard)와 동적 라우트(예: /product/[id])를 포함합니다.
  • as - url에 대한 선택적 데코레이터입니다. Next.js 9.5.3 이전에는 동적 라우트를 미리 가져오는 데 사용되었습니다.
  • options - 다음 필드를 허용하는 선택적 객체입니다:
  • locale - 현재 활성화된 로케일과 다른 로케일을 제공할 수 있습니다. false인 경우, url에 로케일을 포함해야 합니다. 활성 로케일이 사용되지 않기 때문입니다.

예를 들어, 로그인 페이지가 있고 로그인 후 사용자를 대시보드로 리다이렉트한다고 가정해 봅시다. 이 경우, 다음 예시와 같이 대시보드를 미리 가져와 더 빠른 전환을 할 수 있습니다:

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) => {
      // 이미 미리 가져온 대시보드 페이지로 빠른 클라이언트 측 전환을 수행합니다
      if (res.ok) router.push('/dashboard')
    })
  }, [])
 
  useEffect(() => {
    // 대시보드 페이지를 미리 가져옵니다
    router.prefetch('/dashboard')
  }, [router])
 
  return (
    <form onSubmit={handleSubmit}>
      {/* Form fields */}
      <button type="submit">Login</button>
    </form>
  )
}

router.beforePopState

일부 경우(Custom Server를 사용하는 경우 등)에는 popstate (opens in a new tab)를 감지하고 라우터가 이를 처리하기 전에 무언가를 수행하고 싶을 수 있습니다.

router.beforePopState(cb)
  • cb - 들어오는 popstate 이벤트에 대해 실행할 함수입니다. 이 함수는 다음 속성을 가진 객체로 이벤트의 상태를 받습니다:
  • url: String - 새로운 상태에 대한 라우트입니다. 보통 page의 이름입니다.
  • as: String - 브라우저에 표시될 URL입니다.
  • options: Object - router.push에 의해 전송된 추가 옵션입니다.

cbfalse를 반환하면 Next.js 라우터는 popstate를 처리하지 않으며, 그 경우 직접 처리해야 합니다. 파일 시스템 라우팅 비활성화하기를 참조하세요.

beforePopState를 사용하여 요청을 조작하거나 SSR 새로고침을 강제할 수 있습니다. 다음 예시를 참조하세요:

import { useEffect } from 'react'
import { useRouter } from 'next/router'
 
export default function Page() {
  const router = useRouter()
 
  useEffect(() => {
    router.beforePopState(({ url, as, options }) => {
      // 이 두 라우트만 허용하고 싶습니다!
      if (as !== '/' && as !== '/other') {
        // SSR이 잘못된 라우트를 404로 렌더링하도록 합니다.
        window.location.href = as
        return false
      }
 
      return true
    })
  }, [router])
 
  return <p>Welcome to the page</p>
}

router.back

히스토리에서 뒤로 이동합니다. 브라우저의 뒤로 가기 버튼을 클릭하는 것과 동일합니다. 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

현재 URL을 새로고침합니다. 브라우저의 새로고침 버튼을 클릭하는 것과 동일합니다. 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

Next.js 라우터 내에서 발생하는 다양한 이벤트를 감지할 수 있습니다. 지원되는 이벤트 목록은 다음과 같습니다:

  • routeChangeStart(url, { shallow }) - 라우트 변경이 시작될 때 발생합니다.
  • routeChangeComplete(url, { shallow }) - 라우트 변경이 완전히 완료되었을 때 발생합니다.
  • routeChangeError(err, url, { shallow }) - 라우트 변경 중 오류가 발생하거나 라우트 로드가 취소되었을 때 발생합니다.
  • err.cancelled - 내비게이션이 취소되었는지를 나타냅니다.
  • beforeHistoryChange(url, { shallow }) - 브라우저의 히스토리를 변경하기 전에 발생합니다.
  • hashChangeStart(url, { shallow }) - 해시가 변경되지만 페이지는 변경되지 않을 때 발생합니다.
  • hashChangeComplete(url, { shallow }) - 해시가 변경되었지만 페이지는 변경되지 않았을 때 발생합니다.

알아두면 좋은 점: 여기서 urlbasePath를 포함한 브라우저에 표시되는 URL입니다.

예를 들어, 라우터 이벤트 routeChangeStart를 감지하려면 pages/_app.js를 열거나 생성하고 다음과 같이 이벤트를 구독하세요:

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)
 
    // 컴포넌트가 언마운트되면
    // `off` 메서드로 이벤트 구독을 해제합니다:
    return () => {
      router.events.off('routeChangeStart', handleRouteChange)
    }
  }, [router])
 
  return <Component {...pageProps} />
}

이 예제에서는 Custom App (pages/_app.js)을 사용하여 이벤트를 구독합니다. 이는 페이지 내비게이션 시 언마운트되지 않기 때문입니다. 하지만 애플리케이션의 어떤 컴포넌트에서도 라우터 이벤트를 구독할 수 있습니다.

라우터 이벤트는 컴포넌트가 마운트될 때(useEffect (opens in a new tab) 또는 componentDidMount (opens in a new tab) / componentWillUnmount (opens in a new tab))나 이벤트가 발생하는 시점에 직접 등록해야 합니다.

라우트 로드가 취소되면(예: 두 개의 링크를 연속해서 빠르게 클릭한 경우) routeChangeError가 발생합니다. 그리고 전달된 err는 다음 예시와 같이 cancelled 속성이 true로 설정됩니다:

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)
 
    // 컴포넌트가 언마운트되면
    // `off` 메서드로 이벤트 구독을 해제합니다:
    return () => {
      router.events.off('routeChangeError', handleRouteChangeError)
    }
  }, [router])
 
  return <Component {...pageProps} />
}

Potential ESLint errors

router 객체에서 접근 가능한 특정 메서드들은 Promise를 반환합니다. no-floating-promises (opens in a new tab) ESLint 규칙을 사용 중이라면, 전역적으로 또는 해당 라인에 대해 이 규칙을 비활성화하는 것을 고려해보세요.

애플리케이션에 이 규칙이 필요한 경우, Promise를 void 처리하거나 async 함수를 사용하여 Promise를 await한 후 함수 호출을 void 처리해야 합니다. 이는 onClick 핸들러 내부에서 메서드가 호출될 때는 적용되지 않습니다.

영향을 받는 메서드들은 다음과 같습니다:

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

Potential solutions

import { useEffect } from 'react'
import { useRouter } from 'next/router'
 
// 여기서 user를 가져와 반환합니다
const useUser = () => ({ user: null, loading: false })
 
export default function Page() {
  const { user, loading } = useUser()
  const router = useRouter()
 
  useEffect(() => {
    // 다음 줄의 린팅을 비활성화합니다 - 가장 깔끔한 해결책입니다
    // eslint-disable-next-line no-floating-promises
    router.push('/login')
 
    // router.push가 반환하는 Promise를 void 처리합니다
    if (!(user || loading)) {
      void router.push('/login')
    }
    // 또는 async 함수를 사용하고, Promise를 await한 다음 함수 호출을 void 처리합니다
    async function handleRouteChange() {
      if (!(user || loading)) {
        await router.push('/login')
      }
    }
    void handleRouteChange()
  }, [user, loading])
 
  return <p>Redirecting...</p>
}

withRouter

useRouter가 적합하지 않은 경우, withRouter를 사용하여 동일한 router object를 어떤 컴포넌트에도 추가할 수 있습니다.

Usage

import { withRouter } from 'next/router'
 
function Page({ router }) {
  return <p>{router.pathname}</p>
}
 
export default withRouter(Page)

TypeScript

withRouter를 클래스 컴포넌트와 함께 사용하려면, 컴포넌트가 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)