本文概括
- Page Router vs App Router :老版用
pages
目录,新版用app
目录并通过约定文件定义路由和特殊功能。 - page.tsx:定义具体页面,对应一个路由。
- layout.tsx :定义可复用的页面布局,子页面会被包裹其中,根布局必须包含
html
和body
。 - template.tsx:类似布局但不会保留状态,每次路由切换都会重新渲染。
- loading.tsx:在页面数据加载时显示的过渡界面。
- error.tsx :客户端错误边界组件,捕获运行时错误并支持
reset
重试。 - global-error.tsx:根目录的全局错误捕获页面。
- not-found.tsx :定义 404 页面,用于未匹配路由或显式调用
notFound()
。
老版本的Next.js使用的是Page Router,在 pages
目录下,每个js文件就是一个路由,这就导致一些组件不能写在 pages
目录下,新版本换成了App Router ,文件放在 app
目录下,目录下的 page.tsx
就是代表一个路由,Next.js约定了一些特殊的文件:
布局(layout.tsx)、模板(template.tsx)、加载状态(loading.tsx)、错误处理(error.tsx)、404(not-found.tsx)页面(page.tsx)
页面page.tsx
每个目录下的 page.tsx
会映射到一个路由,需要导出一个默认函数,例如:
javascript
export default function Page(){
return <>Next.js</>
}
布局layout.tsx
layout.tsx
文件导出一个React组件,接受 chidren
作为参数,表示的是子页面内容,子页面会拥有layout里的布局,也就是layout会包裹着page页面
javascript
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div>
<h1>Test Layout</h1>
{children}
</div>
)
}
根布局要求:
- 必须有根布局
app/layout.tsx
- 必须包含
html
和body
标签
模版template.tsx
模版和layout类似,会包裹每个页面,但是和layout的区别是,模版不会维持状态,每次进入一个新的路由都会重新初始化,模版会被layout包裹起来。
例如在layout文件里写一个 表单,那么通过Link跳转到子路由,表单里的内容不会变,如果是使用template,那么就会重新渲染,表单里的数据消失
例如 layout.tsx
:
javascript
'use client'
import React, { useState } from 'react'
export default function Layout({ children }: { children: React.ReactNode }) {
const [count, setCount] = useState(0)
return (
<div>
<h1>Test Layout</h1>
<>Layout Count: {count}</>
<button onClick={() => setCount(count + 1)}>Layout数字增加</button>
<br />
{children}
</div>
)
}
template.tsx
javascript
'use client'
import React, { useState } from 'react'
export default function Template({ children }: { children: React.ReactNode }) {
const [count, setCount] = useState(0)
return (
<div>
<>Template Count: {count}</>
<h1>Test Template</h1>
<button onClick={() => setCount(count + 1)}>Template数字增加</button>
{children}
</div>
)
}
当我们增加了数字之后,在test目录路由下跳转,会发现layout里的数字不变,template里的数字会清空
加载loading.tsx
loading.tsx
是加载页面,例如:
javascript
export default function Loading() {
return (
<div>
<h1>Loading</h1>
</div>
)
}
这样在 page.tsx
加载数据的时候,在没拿到数据之前,就会显示loading的内容:
javascript
import Link from 'next/link'
async function getUser() {
await new Promise((resolve) => setTimeout(resolve, 2000))
return {
name: 'cxk',
}
}
export default async function Page() {
const { name } = await getUser()
return (
<div>
<Link href="/test/test2">跳转Page2</Link>
<Link href="/test">跳转Page</Link>
{name}
</div>
)
}
如果一个目录下有很多约定的文件,那么他们的层级是:
Next.js文件约定

错误error.tsx
错误页面必须是客户端组件, error.tsx
接受一个error和reset
-
error: Error
捕获到的错误对象,可能带
digest
(Next.js 内部生成的唯一标识符)。 -
reset: () => void
调用它可以 重置错误边界,让 Next.js 重新尝试渲染页面(比如在用户点 "Retry" 按钮时)。
javascript
'use client'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
const handleReset = () => {
console.log('reset')
reset()
}
return (
<div>
<h1>Error</h1>
<p>{error.message}</p>
<button onClick={handleReset}>Reset</button>
</div>
)
}
可以在页面获取数据的时候抛出异常试试:
javascript
async function getUser() {
await new Promise((resolve) => setTimeout(resolve, 2000))
throw new Error('test error')
return {
name: 'cxk',
}
}
export default async function Page() {
const { name } = await getUser()
return (
<div>
<Link href="/test/test2">跳转Page2</Link>
<Link href="/test">跳转Page</Link>
{name}
</div>
)
}
注意:错误边界不可以捕获同级的layout和tempalte,必须在父级的error.tsx去捕获,因为 ErrorBoundary
被Layout和Template包裹了。
根目录的捕获可以使用 global-error.tsx
进行捕获
404not-found.tsx
未找到页面会显示404页面的内容,触发情况主要有两种:
- 组件调用
notFound
函数 - 路由地址不匹配