Next.js is a full-stack framework for React, primarily focused on server-side rendering (SSR). It has a very powerful, yet somewhat unusual, routing mechanism. What does this routing mechanism look like? And why is it considered strange? Let's try it out and see. First, let's create a Next.js project:
npx create-next-app@latest
Run create-next-app
and enter some basic information, and your Next.js project will be set up.
Next, navigate into the project directory and run npm run dev
to start the development server:
If you can see the page in your browser, that means it’s running successfully:
In the project directory, you'll find an app
folder under src
, which contains a page.tsx
file:
Let's add a few directories:
/nextjser/handsome/page.tsx
export default function Handsome() {
return <div>handsome</div>
}
/nextjser/nice/page.tsx
export default function Nice() {
return <div>nice</div>
}
Then visit the following link in browser:
You can see that by adding a few directories, several corresponding routes are automatically created.
This is Next.js's file system-based routing.
Having just learned that page.tsx
is used to define pages, what if multiple pages have common parts?
For example, what about menus and navigation like this:
Definitely not one copy per page.
This kind of definition goes inside layout.tsx
.
app/layout.tsx
is for defining the outermost layout:
That is, the HTML structure, as well as information like title and description:
You can see these in the HTML source code of the web page:
Not only can the root route define a layout, but every level can as well:
export default function Layout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<div>
<div>Left menu</div>
{children}
</div>
);
}
Next.js will automatically wrap the page.tsx
components with the layout.tsx
components on the outer layer.
Some friends might notice a gradient background, which is defined in global.css
. Let's remove it:
Then continue to look at:
We can use the Link component to navigate between different routes:
Dynamic Routes
Some friends might say, "This is all quite normal."
So let's look at something not so normal next:
If I want to define a page for a route like /dong/111/xxx/222
(where 111
and 222
are parameters in the path), how should I write it?
You could like this:
interface Params {
params: {
param1: string;
param2: string;
}
}
export default function Xxx(
{ params }: Params
) {
return <div>
<div>xxx</div>
<div>params:{JSON.stringify(params)}</div>
</div>
}
Parameters in the path are named using the [xxx] notation.
Next.js will extract the parameters from the path and pass them into the component:
This is called a dynamic route.
Catch-all Segments
What if you want both /dong2/a/b/c
and /dong2/a/d/e
to render the same component?
You can code it like this:
interface Params {
params: {
dong: string;
}
}
export default function Dong2(
{ params }: Params
) {
return <div>
<div>dong2</div>
<div>params:{JSON.stringify(params)}</div>
</div>
}
The syntax [...slug] is used to define routes with arbitrary depth, known as catch-all routes.
You can see that any route under /dong2
will render this component.
Optional Catch-all Segments
What if I directly access /dong2
?
You can see that it results in a 404 error.
But this can also be supported; just add another set of brackets to make it [[...slug]]
, and it will work:
With this change, /dong2
will also render this component, although the parameters will be empty.
This type of route, [[...dong]], is called an optional catch-all.
Route Groups
You can see that the directories in a Next.js project are not just simple directories; they have corresponding routing implications.
But what if I just want to add a plain directory that is not included in the routing?
You can write it like this:
I added a directory called "(dongGroup)" outside of both dong and dong2; will the previous routes change?
I tried it, and it remains unchanged.
This means that as long as you add a () around the directory name, it won't be included in the routing; it's just for grouping purposes, which is called a routing group.
Now, we have rendered a single page under one layout.
Parallel Routes
But what if I want one layout to render multiple pages?
You can write it like this:
Under the parallel
directory, there are 3 pages: page.tsx
, @aaa/page.tsx
, and @bbb/page.tsx
.
They will be passed into layout.tsx
with the parameters children
, aaa
, and bbb
, respectively.
layouts
export default function Layout({
children,
aaa,
bbb
}: {
children: React.ReactNode,
aaa: React.ReactNode,
bbb: React.ReactNode
}) {
return (
<div>
<div>{children}</div>
<div>{aaa}</div>
<div>{bbb}</div>
</div>
)
}
page.tsx
export default function Page() {
return <div>page</div>
}
@aaa/page.tsx
export default function Aaa() {
return <div>aaa</div>
}
@bbb/page.tsx
export default function Bbb() {
return <div>bbb</div>
}
The rendering will look like this:
You can see that in the layout, the contents of 3 pages are included, all rendered out. This is called parallel routing.
Some friends might ask, can I access /parallel/@aaa
?
No, it is not possible.
Additionally, Next.js has a very powerful routing mechanism:
Intercepting Routes
There was such a route before:
We define a route at the same level as it:
import Link from "next/link";
export default function Go() {
return <div>
<div>
<Link href="/nextjser/liu">to nice</Link>
</div>
<div>go</div>
</div>
}
Clicking the link will take you to http://localhost:3000/nextjser/nice
There's no problem with that.
But what if I add a directory named (..)nice
under go
:
At this point, try it again:
You can see that this time the rendered nice
component has been replaced, but if you refresh, it's still the previous component.
Many people might wonder, what's the use of this?
A scenario example will make it clear.
For instance, with a table, when you click on each item, an edit dialog pops up. This edit page can be shared, and when the shared link is opened, it shows the complete edit page.
That is to say, in different scenarios, you can override the component rendered by this URL, which is the use of route interception.
The usage is also very simple. Since you want to intercept the /nextjser/nice
route at the upper level, you need to add (..)
in front.
Similarly, (.)xx
represents intercepting the route of the current directory, (..)(..)xx
intercepts the route of the parent's parent directory, and (...)xxx
intercepts the root route.
This kind of route interception is very useful in specific scenarios.
These are the routing mechanisms related to pages, which are quite powerful, aren't they?
APIs
Of course, these routing mechanisms are not only for pages; Next.js can also be used to define APIs such as Get and Post.
Just replace page.tsx
with route.ts
:
import { NextResponse, type NextRequest } from 'next/server';
const data: Record<string, any> = {
1: {
name: 'guang',
age: 20
},
2: {
name: 'dong',
age: 25
}
}
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
return NextResponse.json(!id ? null : data[id]);
}
We have defined the GET method for the /guang3
route, which retrieves data based on the id
.
Let's visit it:
The routing concepts we learned earlier can all be applied to route.ts
.
For example:
[id]
defines a dynamic route parameter, and [...yyy]
matches arbitrary routes.
In the GET
method of route.ts
, you also retrieve them through params
:
import { NextResponse, type NextRequest } from 'next/server';
interface Params {
params: {
id: string;
yyy: string;
}
}
export async function GET(request: NextRequest, { params }: Params) {
return NextResponse.json({
id: params.id,
yyy: params.yyy
});
}
App router & Page router
Do you feel why Next.js is called a full-stack framework rather than just an SSR (Server-Side Rendering) framework?
This is because, in addition to rendering React components, it can also define APIs.
In this way, we have gone through the routing mechanism of Next.js.
This kind of routing mechanism is called the app router, which means the top level is the app
directory:
Previously, there was also a page router
, where the top-level directory was pages
.
These two are just two different file and directory naming conventions; we only need to learn app router
, as it is the latest routing mechanism.
Summary
Let's summarize what we've learned:
-
aaa/bbb/page.tsx
can define the route for/aaa/bbb
. -
aaa/[id]/bbb/[id2]/page.tsx
contains[id]
as dynamic route parameters, which can be accessed within the component. -
aaa/[...xxx]/page.tsx
can match any route like/aaa/xxx/xxx/xxx
, called a catch-all dynamic route. However, it does not match/aaa
. -
aaa/[[...xxx]]/page.tsx
is similar to the above but matches/aaa
as well, called an optional catch-all dynamic route. -
aaa/(xxx)/bbb/page.tsx
where(xxx)
is just for grouping and does not participate in routing, called a routing group. -
aaa/@xxx/page.tsx
can be included multiple times inlayout.tsx
, called parallel routing. -
aaa/(..)/bbb/page.js
can intercept the/bbb
route, rewrite the corresponding component, but after refreshing, it still renders the original component, called an intercepting route.
These routing mechanisms may indeed seem quite peculiar, and they can make a Next.js project look like this:
Compared to this file system-based routing, many might be more familiar with the programmatic routing style of React Router:
Next.js's declarative routing is actually quite convenient once you get used to it.
There's no need to maintain separate routing logic; the directory structure itself defines the routes, making it clear at a glance.
Moreover, these seemingly strange syntaxes actually make sense when you think about them:
For example,
[xxx]
is a common syntax for matching parameters in a URL.[...xxx]
simply adds...
to it, which in JavaScript signifies an arbitrary number of arguments, so it's used to match routes with arbitrary segments.Adding another set of brackets
[[...xxx]]
indicates that the route can be optional, which is also a natural design choice.(.)xx
and(..)xxx
use.
and..
, which are symbols in file systems, making them quite natural for intercepting routes.Routing groups are denoted by parentheses
(xxx)
to indicate grouping, and parallel routes are indicated by@
in@xxx
to show that multiple pages can be included, all of which are intuitive designs.
So, Next.js's implementation of this routing mechanism based on the file system, with these seemingly strange syntaxes, are actually quite reasonable designs.
We've learned about Next.js's routing mechanism, which is defined based on the file system for interfaces or page routes.
Next.js's routing mechanism is quite powerful, supporting many features, These syntaxes may seem a bit odd at first glance, but upon closer consideration, they are quite reasonable designs.
Top comments (0)