Sebastian Gomez
Routing pages with Next.js
You don't need to interact directly with the Next.js router to create and view pages on your site. Next.js was built with conventions to make creating routes as easy as creating a file.
Note, Pages Router vs. App Router: This post teaches the Pages Router (the /pages directory). It's still fully functional and supported, but since Next.js 13 the App Router (the /app directory) is the default and recommended paradigm, and it's what you get when you run create-next-app on Next.js 16. The concepts we'll cover, dynamic segments, catch all routes and optional routes, exist in both; only the file convention changes. Throughout the post I show you the App Router equivalent in each case.
To get started you only need to create a folder called /pages. Next.js will associate any file inside this directory as a route. The file name will also determine the route name or route pattern, and whatever component is exported inside those files will be the page that gets rendered.
For now let's create an "index" route simply by creating the file inside our pages folder: /pages/index.js. Then let's create a component and export it:
export default function IndexPage() {
return <h1>Index Page</h1>
}Start your development server:
# npm
$ npm run dev
# yarn
$ yarn devWe should be able to navigate in our browser to the "index" route and see our h1. As you can see, it's super easy to create a route and have it available in our project almost immediately. This kind of convention makes development faster and lets you focus more on content than on logic.
You'll see that just by opening the browser, the content appears.
Note: The index file is always a special case, even more so in Next.js, since it will render by default just like an HTML file. However, unlike a traditional website or a SPA, the route http://localhost:3000/index does not exist and Next.js will show its default 404 error.
Tip: Remember that Next.js uses the pages folder as its base, so the route http://localhost:3000/pages/index won't exist either unless you create a pages folder inside the main pages folder.
Great, we've created an "index" page, but what about more complex routes like myapp.com/project/settings or myapp.com/user/1, where 1 is a parameter? Don't worry, Next.js has that covered.
Folders and routes
To create a route like /project/settings we can use folders inside our main /pages folder. For our note taking application, we need the following routes for now:
index => /
all notes => /notes
one note => /notes/:idWe've already created the "index" route; let's create the route for all notes. To do so let's create a folder called notes and inside it create a file index.jsx.
pages
index.jsx
notes
index.jsxBy adding an "index" file inside a folder, we're telling Next.js that we want the component inside the index to be what's rendered when this route is accessed by the folder name. So, in this case, navigating to /notes will render the component found in pages/notes/index.jsx. For now, let's put the following inside pages/notes/index.jsx:
export default function AllNotesPage() {
return <h1>All notes Page</h1>
}If you navigate to the route you should see the expected result.
Tip: It's not always necessary to create the folder and the index file; you can also create just the file and Next.js will create the route. For example, pages/notes/index.jsx can easily be replaced by pages/notes.jsx and it will work exactly the same. However, you must be careful with conflicts between routes. It's completely incorrect to have both pages/notes/index.jsx and pages/notes.jsx, since Next.js won't know which of the two components to render for that route.
Dynamic routes
Next.js makes creating dynamic routes easy. Depending on whether you want these pages to be prerendered or not, the strategy inside the component changes; but for now let's focus on pages that are built while the server is running, that is, at our server's runtime and not at compile time or "build time".
To create a dynamic route, we can create a file whose name is the dynamic parameter wrapped in square brackets, like this: [id].jsx. Where id is the parameter name. You can name it whatever you want. Remember that the square brackets are not a mistake or an example: they are the strict Next.js syntax for creating a dynamic route. So, to create our /notes/1 route we'll create the file [id].jsx inside our /notes folder.
pages
index.jsx
notes
index.jsx
[id].jsxWe can access the id parameter inside our page using a hook called useRouter from the next/router module. This already comes available and ready with Next.js.
// Import the 'useRouter' hook from 'next/router' to access the router object
import { useRouter } from 'next/router'
// Named component exported by default
export default function NotePage() {
// Get an instance of the Next.js router
const router = useRouter()
// Extract the 'id' property from the 'query' object to access the value of 'id' in the current route
const { id } = router.query
// Render the value of 'id' obtained from the route
return <h1>Note: {id}</h1>
}Where the parameter name in the router.query object is the same as the parameter inside square brackets in the page name:
router.query.id
|
|
[id].jsxThat's how you'd see the parameter in your application.
App Router equivalent: If your project uses the /app directory, this same case is solved by creating app/notes/[id]/page.jsx and reading the parameter with useParams from next/navigation (in a Client Component) or from the page props (in a Server Component):
>
```jsx 'use client' import { useParams } from 'next/navigation'
>
export default function NotePage() { const { id } = useParams() return <h1>Note: {id}</h1> } ```
>
Note: on Next.js 16, in a Server Component the props.params are obtained asynchronously (const { id } = await params). In Client Components use the useParams() hook.
Capturing all routes
Next.js has a pretty nice feature that helps us define and capture all routes when we really don't want to make a page for every possible route.
What would a capturable route look like? Basically it's all the possible combinations of routes within one, for example:
this/folder/**Where ** means everything inside the "folder". We can do the same thing with the concept of dynamic routes in Next.js. The only thing we need to do is create a file in our directory with a syntax like this: docs/[...params].jsx.
So, Next.js uses the three dots ... to understand that the entire URL structure will be passed as a parameter. The difference inside useRouter will be that the parameters will be an array, in exactly the same order in which the route was built:
import { useRouter } from 'next/router'
// The file name means the component will handle routes of the form /docs/a/b/c
// file => /docs/[...params].jsx
// route => /docs/a/b/c
export default function DocsPage() {
const router = useRouter()
// 'params' will be an array with the route segments
const { params } = router.query
// For the example route /docs/a/b/c => params === ['a', 'b', 'c']
return <h1>hello {JSON.stringify(params)}</h1>
}No matter how many segments your route has, it will always pass them as an array. That's how our component would look.
App Router equivalent: The catch all also exists in /app with the same folder syntax: app/docs/[...params]/page.jsx. There the segments arrive in params (asynchronous in Server Components, or via useParams in Client Components) and params.params will be the array ['a', 'b', 'c'].
Now, for our parent page, that is, for the /docs route on its own, it would still show the 404 error, since we haven't created the index.jsx file inside the folder. However, if we also want to save ourselves from creating this file, we could use another pair of square brackets outside our [...params]:
docs/[[...params]].jsxTip: When should you use this catch all feature? It's really useful when you have a set of pages that have similarities (if not identical ones) in their layouts and styles, but have different content and need their own URL. Documentation and wikis are a perfect example for using this feature.
Components that aren't pages
Pages are special, but what happens when we only need React components to use in our pages? Next.js doesn't have any convention about this. Normally these components are created inside the /src/components folder, where we place our reusable components.
Suggested exercises
- Create a Next.js 16 project with the Pages Router and reproduce the routes
/,/notesand/notes/:idjust as we saw them here. - Convert the dynamic route
/notes/[id]to its App Router equivalent (app/notes/[id]/page.jsx) reading the parameter withuseParams. - Create a documentation section with an optional catch all (
[[...params]]) that shows both/docsand/docs/a/b/cfrom a single file.
3-point summary
- In the Pages Router, every file inside
/pagesautomatically becomes a route;indexis the root route of its folder. - Dynamic routes use brackets in the file name:
[id].jsxfor one parameter,[...params].jsxto capture all routes and[[...params]].jsxto make it optional. - The App Router (
/app) is the default paradigm in Next.js 16; the same concepts apply by swappingpages/forapp/.../page.jsxandrouter.queryforuseParams.
That's all, I hope this post is useful to you and that you can apply it to a project you have in mind, or that it simply helped you understand how routing works in Next.js.
Leave me a comment if it helped, if you want to add an opinion, or if you have any questions. And remember, if you liked it, you can also share it using the social links below. Good luck!
Sebastian Gomez
Creador de contenido principalmente acerca de tecnología.