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.tsxcan define the route for/aaa/bbb. -
aaa/[id]/bbb/[id2]/page.tsxcontains[id]as dynamic route parameters, which can be accessed within the component. -
aaa/[...xxx]/page.tsxcan match any route like/aaa/xxx/xxx/xxx, called a catch-all dynamic route. However, it does not match/aaa. -
aaa/[[...xxx]]/page.tsxis similar to the above but matches/aaaas well, called an optional catch-all dynamic route. -
aaa/(xxx)/bbb/page.tsxwhere(xxx)is just for grouping and does not participate in routing, called a routing group. -
aaa/@xxx/page.tsxcan be included multiple times inlayout.tsx, called parallel routing. -
aaa/(..)/bbb/page.jscan intercept the/bbbroute, 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.(.)xxand(..)xxxuse.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@xxxto 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)