In Next.js, a page is a React Component exported from a file (.js
, .jsx
, .ts
, or .tsx
) in the pages
directory. The component can have any name, but you must export it as a default
export.
export default function About() { return <div>About</div> }
Pages for static routes
pages/index.js
→ maps to/
routepages/posts/first-post.js
→ maps to/posts/first-post
Pages for dynamic routes
Sometimes routes have dynamic segments, for example if the route uses a blog title that is read from the database and the title is supposed to be used in the page. In that case you have to use bracket-syntax:
pages/blog/[slug].js
→/blog/:slug
(e.g./blog/hello-world
)pages/[username]/settings.js
→/:username/settings
(e.g./foo/settings
)pages/post/[...all].js
→/post/*
(e.g./post/2020/id/title
)
Catch-All Routes
Dynamic routes can be extended to catch all paths by adding three dots (...
) inside the brackets: pages/post/[...slug].js
matches /post/a
, but also /post/a/b
, /post/a/b/c
and so on. The resulting query object would be:
// the file pages/post/[...slug].js // if called as /post/a would result as query: { "slug": ["a"] } // if called as /post/a/b would result as query: { "slug": ["a", "b"] }
Catch-All Routes that are optional
Catch all routes can be made optional by including the parameter in double brackets ([[...slug]]
).
For example, pages/post/[[...slug]].js
will match /post
, /post/a
, /post/a/b
, and so on.
The main difference between catch all and optional catch all routes is that with optional, the route without the parameter is also matched. The query
objects are as follows:
// GET `/post` (empty object) { } // `GET /post/a` (single-element array) { "slug": ["a"] } // `GET /post/a/b` (multi-element array) { "slug": ["a", "b"] }
Note: Predefined routes take precedence over dynamic routes, and dynamic routes over catch a
Metadata in <Head>
You change metadata by using <Head>
Component instead of <head>
element.
import Head from 'next/head' export default function FirstPost() { return ( <> <Head> <title>First Post</title> </Head> <h1>First Post</h1> </> ) }
Linking to static routes
Any <Link />
in the viewport (initially or through scroll) will be prefetched by default (including the corresponding data) for pages using Static Generation. The corresponding data for server-rendered routes is fetched only when the <Link />
is clicked.
import Link from 'next/link' export default function FirstPost() { return ( <> <Link href="/">Home</Link> </> ) }
Linking to dynamic routes
import Link from 'next/link' function Posts({ posts }) { return ( <ul> {posts.map((post) => ( <li key={post.id}> <Link href={`/blog/${encodeURIComponent(post.slug)}`}> <a>{post.title}</a> </Link> </li> ))} </ul> ) } export default Posts
Or alternatively use a URL object:
<Link href={{ pathname: '/blog/[slug]', query: { slug: post.slug }, }} >
If you need to link to an external page outside the NextJS app, just use an <a>
tag without Link
. If you need to add attributes like, for example, className
, add it to an a
tag within the Link element, not to the Link
tag itself.
Linking imperatively
<Link>
should be able to cover most of your routing needs, but you can also do client-side navigation imperatively:
import { useRouter } from 'next/router' function ReadMore() { const router = useRouter() return ( <span onClick={() => router.push('/about')}>Click here to read more</span> ) } export default ReadMore
Change URL without fetching data again
Also called Shallow Routing. getServerSideProps
, getStaticProps
, and getInitialProps
will not be called, but you will still receive the updated pathname
and the query
via the router
object when you set the shallow
option to true
:
import { useEffect } from 'react' import { useRouter } from 'next/router' // Current URL is '/' function Page() { const router = useRouter() useEffect(() => { // Always do navigations after the first render router.push('/?counter=10', undefined, { shallow: true }) }, []) useEffect(() => { // The counter changed! }, [router.query.counter]) } export default Page
Shallow routing only works for same-page URL changes.
next/router
Introducing the 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
The router
object returned by useRouter
:
// Current route. That is the path of the page in /pages pathname: String // The query string parsed to an object. It will be an empty object during prerendering if the page doesn't have data fetching requirements. Defaults to {} query: Object // The path (including the query) shown in the browser without the configured basePath or locale. asPath: String // Whether the current page is in fallback mode. isFallback: boolean // The active basePath (if enabled). basePath: String // The active locale (if enabled) locale: String // All supported locales (if enabled). locales: String[] // The current default locale (if enabled) defaultLocale: String // Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server. isReady: boolean // Whether the application is currently in preview mode. isPreview: boolean
Push
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(() => { if (!(user || loading)) { router.push('/login') } }, [user, loading]) return <p>Redirecting...</p> }
Replace
import { useRouter } from 'next/router' export default function Page() { const router = useRouter() return <button onClick={() => router.replace('/home')}>Click me</button> }
beforePopState
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 }) }, []) return <p>Welcome to the page</p> }
There is also router.back()
, router.reload()
.
You can listen to routing 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 just 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