Cheatsheet
app/folderA/folderB/page.js
. App routing, where the router is{domain}/folderA/folderB
.app/folderA/[id]/page.js
. Dynamic routing, matching any{domain}/folderA/{any id}
.app/folderA/[...id]
. A catch-all for sub-routes, capturing any level of routes, equating to matches like{domain}/folderA/aaa/bbb/ccc
. However, accessing{domain}/folderA
results in a 404.app/folderA/[[...id]]
. Besides catch-all for sub-routes, it includes that level’s route (domain/folderA), though the params will be empty.(folderA)
. Simply adding a folderA directory, not included in the route. // TODO:@name
. Parallel routes, allowinglayout.tsx
to render multiplepage.tsx
.(.)name
. Intercepting routes, presenting different displays between router and direct access. The brackets denote a relative path.
Make Some Examples
Basic App Routes
Under the ./app/...
directory, each directory acts as a sub-route, enabling the direct creation of nested routes using directories and subdirectories.
Dynamic Routes-general
In many real-world scenarios, routes are dynamicaly generated, such as bolg post IDs. For accessing a blog post via https://{domain}/blog/{blogID}
, the NextJS App Routing would be:
.
└── app
└── blog
└── [blogID]
└── page.tsx
Dynamic Routes - catch all
The catch all proivides tremendous flexibility for dynamic routes. For instance, on an e-commerce site, users could access {domain}/products/electronics/phones
or even deeper {domain}/products/electronics/phones/samsung
with a single component by using catch all to capture and dynamically display based on the route. As per the example, if the app routing is:
.
└── app
└── products
└── [...data]
└── page.tsx
Then, the params for /products/electronics/phones
would be:
{ data: ["electronics", "phones"] }
And for products/electronics/phones/samsung
:
{ data: ["electronics", "phones", "samsung"] }
However, directly entering {demain}/products
in the browser would result in a 404
.
Dynamic Routes - optional catch all
Optional Catch-all in dynamic routes aims to address this issue. Modifying the directory to:
.
└── app
└── products
└── [[...data]]
└── page.tsx
Now allows direct access to {demain}/products
, although the params would be an empty object, {}
.
Routes Group
This is handy for organizing project folders. Imagine a dashboard project requiring login, whrere sign-in and sign-up share one layout, and dashboard, services, settings use another one, all without needing new main routes(all are top-level routes). The directory structure would ideally be:
.
└── app
├── dashboard
├── services
├── settings
├── sign-in
└── sign-up
However, adding layout according to requirements would introduce a lot of redundant code:
.
└── app
├── dashboard
│ ├── layout.tsx
│ └── page.tsx
├── services
│ ├── layout.tsx
│ └── page.tsx
├── settings
│ ├── layout.tsx
│ └── page.tsx
├── sign-in
│ ├── layout.tsx
│ └── page.tsx
└── sign-up
├── layout.tsx
└── page.tsx
In such scenarios, implying the Routes Group method greatly improves readability and reduces duplicate layout.tsx
.
.
└── app
├── (auth)
│ ├── layout.tsx
│ ├── sign-in
│ │ └── page.tsx
│ └── sign-up
│ └── page.tsx
└── (main)
├── dashboard
│ └── page.tsx
├── layout.tsx
├── services
│ └── page.tsx
└── settings
└── page.tsx
Parallel Routes
Parallel routing feels like Vue’s <slot/>
to me, allowing retrieval of @folderName
defined in the directory within the same level layout.tsx
(the nearst parent layout.tsx). For example, wiht the directory structure:
.
└── app
├── @auth
│ └── page.tsx
├── @user
│ └── page.tsx
├── layout.tsx
└── page.tsx
And then we could capture these named slots in layout.tsx
:
export default function Layout({
children,
auth,
user,
}: {
children: ReactNode
auth: ReactNode
user: ReactNode
}) {
return (
<>
{children}
{auth}
{user}
</>
)
}
This setup allows auth
and user
to excute their own stream SSR if they have their loading.tsx
. Also, conditioal rendering could be applied in layout.tsx
(assuming different layouts for auth and user):
export default function Layout({
children,
auth,
user,
}: {
children: ReactNode
auth: ReactNode
user: ReactNode
}) {
return (
<>
{children}
{ condition? auth : user}
</>
)
}
However, only becoming familiar with NextJS’s approach could make this more convenient; otherwise, direct <Suspense/>
and conditional rendering could suffice. For instance, changing the directory to:
.
└── app
├── _components
│ ├── auth-loding.tsx
│ ├── auth.tsx
│ ├── user-loading.tsx
│ ├── user.tsx
│ └── index.tsx
├── layout.tsx
└── page.tsx
And then manually writing <Suspense/>
in layout.tsx
:
import { Suspense } from 'react'
import { Auth, AuthLoading, User, UserLoading } from './_components'
export default function Layout({children}:{children:ReactNode}) {
return (
<>
{children}
<Suspense fallback={<AuthLoading/>}>
<Auth />
</Suspense>
<Suspense fallback={<UserLoading/>}>
<User />
</Suspense>
</>
)
}
Would also be a feasible approach, not to mention pure React of conditional rendering. This part might warrant further exploration.
Intercepting Routes
I haven’t yet had the chance to use intercepting routes, so let’s just demo it quickly:
Directory structure:
. └── app └── folderA ├── (.)folderB │ └── page.tsx ├── folderB │ └── page.tsx └── page.tsx
folderA/page.tsx
:"use client"; import { useRouter } from "next/navigation"; export default function Page() { const router = useRouter(); const goToFolderB = () => { router.push("folderA/folderB"); }; return ( <div> <h1>FolderA</h1> <button onClick={goToFolderB}>route to folderB</button> </div> ); }
folderA/folderB/page.tsx
export default function Page() { return <h1>Original FolderB</h1>; }
folderA/(.)folderB/page.tsx
export default function Page() { return <h1>(.) Intercepted FolderB</h1>; }
Ine the demo project above, if one navigates from {domain}/folderA
to {domain}/folderB
via button click, the content displayed will be (.) Intercepted FolderB. However, if entering {domain}/folderA/folderB
directly into the browser, the display will be Original FolderB, indicating that a context-aware scenario (like roue transitions), the route will be intercepted and render (.)folder/page.tsx
.
Conclusion
Getting used to NextJS App Routing might take some time initially (especially if trying to apply one’s own logic, which could lead to some code redundancy), but once accustomed, you’ll find NextJS truly optimizes for engineer usability, front-end scenarios, and more. Looking forward to creating more exciting projects with NextJS :)
Ref
ChangeLog
- 20240405-初稿