Next.js has always been a file-based routing system, though the syntax has changed over time. With the introduction of the App Router, file-based routing continues to be an intuitive way to define routes in your application.
File-Based Routing
File-based routing in Next.js means that the location of the page.tsx
or route.ts
file inside the app
directory defines how it maps to a given URL. By organizing your files and directories in a specific way, you can create routes that correspond to the desired URLs.
Let's build a quick demo app to experiment with the different types of routes you can define in a Next.js application using the App Router.
Creating the Demo App
To create a new demo app, run the following command:
pnpm dlx create-next-app@latest --use-pnpm route-test
Like before, say "yes" to all of the options you are presented, then navigate into the route-test
directory and open it in your code editor.
Basic Routes
The most basic route in a Next.js application is the homepage, which is defined by the src/app/page.tsx
file. This file maps directly to the root URL (/
).
When we trim down the page.tsx
file to just say "Home page", we'll see it displayed when we visit the root URL.
// inside page.tsx
export default function Home() {
return (
<main className="...">
<h1 className="...">Home page</h1>
</main>
);
}
Special Properties
Every page.tsx
route receives two special properties: params
and searchParams
. For the homepage, we don't have any parameterized routes, so we'll focus on searchParams
.
Here's how we would add the searchParams
property to the Home
component:
export default function Home({ searchParams }: { searchParams: Record<string, string | string[]> }) {
return (
<main>
<h1>Homepage</h1>
<h2>Search Params:</h2>
<pre>{JSON.stringify(searchParams, null, 2)}</pre>
</main>
);
}
If we add search parameters to the URL, such as ?q=5&a=10
, the searchParams
object will contain the corresponding key-value pairs. If a parameter appears multiple times in the URL, its value will be an array of strings.
Nested Routes
To create a specific route within the application, we can nest directories inside the app
directory. For example, to create an /about
route for the application, we would create a src/app/about/page.tsx
file.
This can be further nested to create more complex routes. For instance, an /about/you
route would be defined by the src/app/about/you/page.tsx
file.
Copying and pasting the page.tsx
contents into these files would display the corresponding content when visiting the respective URLs:
Parameterized Routes
Parameterized routes allow you to handle dynamic segments in the URL. To create a parameterized route, you can use brackets ([]
) in the directory name to specify a dynamic segment. For example, to create a /product/:productId
route, you would create a file at app/product/[productId]/page.tsx
.
The page.tsx
file for a parameterized route receives a params
prop, which is an object containing the dynamic segments as keys:
export default function ProductDetail({
params,
searchParams,
}: {
params: { productId: string };
searchParams: Record<string, string | string[]>;
}) {
return (
<main>
<h1>Product Detail Page</h1>
<p>Params</p>
<pre>{JSON.stringify(params, null, 2)}</pre>
<h2>Search Params</h2>
<pre>{JSON.stringify(searchParams, null, 2)}</pre>
</main>
);
}
Now if we visit /product/foo
, the params
object will have a productId
property with the value "foo"
:
However, navigating to /product
will give us a 404 erros since no page is defined for that route. In order to fix this, we'll have to create a /product/page.tsx
file for the Product Home Page.
Catch-All Routes
Catch-all routes allow you to match any number of URL segments after a certain point. To create a catch-all route, you can use the ...
syntax in the directory name, followed by a parameter name.
For example, say we wanted to create a setup that would allow for routes for /setting/a
, /setting/a/b
, /setting/a/b/c
, and so on. We would create a src/app/setting/[...setting]/page.tsx
file.
The page.tsx
file for a catch-all route receives a params
prop with an array of the matched segments:
export default function SettingPage({
params,
searchParams,
}: {
params: { setting: string[] };
searchParams: Record<string, string | string[]>;
}) {
return (
<main>
<h1>Setting Page</h1>
<p>Setting: {JSON.stringify(params.setting)}</p>
</main>
);
}
If we visit /setting/a/b/c
, the params
prop will be { setting: ["a", "b", "c"] }
.
Like before, a page.tsx
file will have to be created in order to have a Setting Home Page.
Optional Catch-All Routes
Optional catch-all routes allow you to match any number of URL segments after a certain point, including the case where no additional segments are provided. To create an optional catch-all route, you can use double brackets ([[...slug]]
) in the directory name.
For example, for an Info page we could create a src/app/info/[[...item]]/page.tsx
file.
If we visit /info
, the params
prop will be undefined
. If we visit /info/a/b/c
, the params
prop will be { item: ["a", "b", "c"] }
.
Route Groups
Route groups allow you to create a maintainable structure inside the app
directory by grouping related routes together. To create a route group, you can wrap the folder name in parentheses like app/(teamA)/editor/page.tsx
The teamA
directory is not included in the final URL, so (teamA)/editor
corresponds to /editor
.
Routing Cheatsheet
Through these examples, we've created a useful routing cheatsheet:
Here's the fixed table with proper markdown formatting:
Path | URL Examples |
---|---|
/page.tsx | / |
/about/page.tsx | /about |
/about/you/page.tsx | /about/you |
/product/[productId]/page.tsx | /product/foo , /product/bar |
/product/page.tsx | /product |
/setting/[...setting]/page.tsx | /setting/a , /setting/b/c |
/setting/page.tsx | /setting |
/info/[[...item]]/page.tsx | /info , /info/23 , /info/23/detail |
/(teamA)/editor/page.tsx | /editor |
In addition to page.tsx
files, you can also use route.ts
files to create route handlers at specific URLs. However, you can only have either a page.tsx
file or a route.ts
file for a given route, not both.