文章
next js Routing 学习笔记 01
React Server Components (RSC)
- Server Components
- Client Components
服务端组件(Server Components)
- Next js 默认视作 服务端组件
- 服务端组件 可以执行 服务侧的任务,比如文件读取或者读写数据库
- 但是不能处理用户交互
客户端组件(Client Components)
- 需要明确首行使用 use client 标识
- 不能执行服务端任务 比如读取文件
- 但是可以与用户交互
路由
基于文件系统的路由系统。
- 所有的路由必须在/app路径下
- 路由文件命名:page.js或者page.jsx
- 每层文件夹代表url一部分路径

嵌套路由

动态路由

获取动态路由参数
type Props = {
params: Promise<{ productId: string }>;
};
export default async function ProductDetails({ params }: Props) {
const productId = (await params).productId;
return <h1>Details about product {productId}</h1>;
}
- async 标记异步函数
- {params} 函数传参 参数类型中指定需要的参数名称和类型Promise<{ productId: string }>
- await params获取参数
嵌套动态路由

获取嵌套路由参数
export default async function ProductReview({
params,
}: {
params: Promise<{ productId: string; reviewId: string }>;
}) {
const { productId, reviewId } = await params;
return (
<h1>
Review {reviewId} for product {productId}
</h1>
);
}
- 同样在参数类型中指定嵌套参数Promise<{ productId: string; reviewId: string }>
获取路由所有分段 catch all segments

文件夹命名格式[...slug]
获取路由参数
export default async function Doc({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params;
if (slug?.length === 2) {
return (
<h1>
Viewing docs for feature {slug[0]} and concept {slug[1]}
</h1>
);
} else if (slug?.length === 1) {
return <h1>Viewing docs for feature {slug[0]}</h1>;
}
return <h1>Docs home page</h1>;
}
- 参数类型 slug: string[] 每一个元素来自url:http://xxx/ 参数1 / 参数2 /
- 可以根据参数数组的长度返回不同的页面响应内容
自定义404页面
在page.tsx同级路径新建 not-found.tsx文件 用于显式404页面显式内容
export default function NotFound() {
return (
<div>
<h2>Page Not Found</h2>
<p>Could not find requested resource</p>
</div>
);
}
显式调用404页面
import { notFound } from "next/navigation";
export default async function ProductReview({
params,
}: {
params: Promise<{ productId: string; reviewId: string }>;
}) {
const { productId, reviewId } = await params;
// 显式调用404页面
if (parseInt(reviewId) > 1000) {
notFound();
}
return (
<h1>
Review {reviewId} for product {productId}
</h1>
);
}
获取url path信息
"use client";
import { usePathname } from "next/navigation";
export default function NotFound() {
const pathname = usePathname();
const productId = pathname.split("/")[2];
const reviewId = pathname.split("/")[4];
// console.log({ pathname, productId, reviewId });
return (
<div>
<h2>
Review {reviewId} not found for product {productId}
</h2>
</div>
);
}
私有文件夹(/app目录下不想被url访问的文件夹)
- 格式:下划线 + 文件夹名称: _lib/
路由分组
- 文件夹命名格式:(分组名称)
- 分组的文件夹名称不参与到URL path
layout

在子页面指定 自己的布局,子目录下添加layout.tsx
多样的layout
比如登陆页面一个布局,登陆后另一个布局

(main)中根路径有layout有page.tsx 所以访问根URL会定位到该page.tsx
metadata
- 静态metadata对象
- 动态生成metadata函数 函数名称必须:generateMetadata
page页面metadata会覆盖layout页面的metadata
定义metadata对象
export const metadata = {
title: "About Codevolution",
};
动态metadata函数
import { Metadata } from "next";
export const generateMetadata = async ({
params,
}: Props): Promise<Metadata> => {
const productId = (await params).productId;
const title = await new Promise((resolve) => {
setTimeout(() => {
resolve(`iPhone ${productId}`);
}, 100);
});
return {
title: `Product - ${title}`,
};
};
resolve 是 Promise 的一个核心函数,它的作用是将 Promise 从 pending(等待)状态变为 fulfilled(已完成)状态,并传递一个结果值
resolve(value) 的参数 value 会成为 Promise 的最终结果,这个值可以被 await 或 .then() 接收。
- new Promise((resolve) => { … }) 创建了一个 Promise,并传入一个执行器函数(executor),这个函数接收 resolve 作为参数。
- setTimeout 模拟异步操作,100ms 后调用 resolve,并传入字符串 iPhone ${productId}。
- await 会等待 Promise 完成,并获取 resolve 传入的值,最终赋值给 title。
title Metadata
import { Metadata } from "next";
export const metadata: Metadata = {
title: {
default: "Next.js Tutorial - Codevolution",
template: "%s | Codevolution",
},
description: "Generated by Next.js",
};
title属性:default、template、absolute
如果在layout中指定
- default 默认显式title内容
- template 使用 %s 占位符 当子路径设置了title会 格式化%s展示内容
- 如果子页面设置absolute 则忽略layout中的配置
Link组件
<>
<Link href="/">Home</Link>
<h1>Product List</h1>
<h2>
<Link href="products/1">Product 1</Link>
</h2>
<h2>
<Link href="products/2">Product 2</Link>
</h2>
<h2>
<Link href="products/3" replace>
Product 3
</Link>
</h2>
<h2>
<Link href={`products/${productId}`}>Product {productId}</Link>
</h2>
</>
replace参数会替换当前的历史记录条目,而不是添加一个新的。
获取当前访问路径
const pathname = usePathname();
const isActive = pathname === link.href || (pathname.startswith(link.href) && link.href !== "/ )
...
<Link className={isActive ? "font-bold mr-4" : "text-blue-500 mr-4"}
href={link.href}
key={link.name}
>
{link.name}
</Link>
params 和 searchParams
- params 获取的是 路由参数: [参数名]
- searchParams 获取的是query参数:?name=xxx
"use client";
import Link from "next/link";
import { use } from "react";
export default function NewsArticle({
params,
searchParams,
}: {
params: Promise<{ articleId: string }>;
searchParams: Promise<{ lang?: "en" | "es" | "fr" }>;
}) {
const { articleId } = use(params);
const { lang = "en" } = use(searchParams);
return (
<div>
<h1>News Article #{articleId}</h1>
<p>Reading in: {lang.toUpperCase()}</p>
{/* Language switcher */}
<div className="language-options">
<Link href={`/articles/${articleId}?lang=en`}>English</Link> |
<Link href={`/articles/${articleId}?lang=es`}>Español</Link> |
<Link href={`/articles/${articleId}?lang=fr`}>Français</Link>
</div>
</div>
);
}
导航页面
"use client"
...
const router = useRouter()
router.push("/")
layout.tsx、template.tsx、page.tsx渲染关系
