1. 简介
- 构建全栈 Web 应用程序的 React 框架
- 使用react来构建用户界面
特性
- 路由:基于
文件系统
的路由器,构建在服务器组件
之上,支持布局、嵌套路由、加载状态、错误处理 - 渲染:使用客户端和服务器进行客户端和服务器渲染。通过 Next.js 在服务器上进行静态和动态渲染进一步优化。在 Edge 和 Node.js 运行时进行流式传输。
- 数据获取:在服务器组件中使用 async/await 简化数据获取,并扩展了
fetch
API 以实现请求缓存、数据缓存和重新验证。 - 样式 : 支持你熟悉的样式方法,包括 CSS Modules、Tailwind CSS 和 CSS-in-JS
- 优化 : 图像、字体和脚本优化,以改善应用程序的核心 Web 指标和用户体验。
- TypeScript : 改进了对 TypeScript 的支持,具有更好的类型检查和更高效的编译,以及自定义 TypeScript 插件和类型检查器。
两种不同的路由:APP Router和Pages Router
2. 安装
- nodejs 18.8 或者更高版本
- 自动安装:npx create-next-app@latest
- 手动安装:npm install next@latest react@latest react-dom@latest
创建app目录结构
Next.js 使用文件系统路由,这意味着应用程序中的路由由你的文件结构决定。
创建一个 app
文件夹。然后,在 app
内创建一个 layout.tsx
文件。这个文件是根布局。它是必需的,并且必须包含 <html>
和 <body>
标签。
app/layout.tsx
TypeScript
TypeScriptJavaScript
export default function RootLayout({ children,}:{ children: React.ReactNode}) {
return(
<html lang="en">
<body>{children}</body>
</html>
)}
创建一个带有初始内容的主页 app/page.tsx
:
app/page.tsx
export default function Page() {
return <h1>Hello, Next.js!</h1>
}
当用户访问你的应用程序根目录(/
)时,layout.tsx
和 page.tsx
都将被渲染。
创建public文件夹(可选)
- 在项目根目录创建一个
public
文件夹,用于存储静态资源,如图片、字体等。public
中的文件可以从基础 URL(/
)开始被你的代码引用。- 例如,
public/profile.png
可以被引用为/profile.png
- 例如,
3. 项目结构
- 顶级文件夹 app : APP Router pages : pages Router public : 要提供的静态资产 src : 可选的应用程序源文件夹
- 文件夹中,只有page.js 和 route.js 返回的内容才会发送到客户端,比如 /dashboard 对应的是文件夹dashboard/page.js 。 api/route.js 客户端可以访问
- 私有文件夹 : 这表示该文件夹是私有实现细节,不应被路由系统考虑,从而将该文件夹及其所有子文件夹排除在路由之外。 比如 : _components文件夹放置组件
- 路由组:(文件夹名) 用括号括起来的文件夹,不会被包含在URL路径中
4. 布局和页面
-
app下直接创建一个page.js 对应的路由是 "/"
-
创建布局:
- 根布局:app下创建一个layout.js
export default function DashboardLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body> {/* Layout UI */} {/* Place children where you want to render a page or nested layout */} <main>{children}</main> </body> </html> ) }
- 上面是根布局,因为在app目录根部,根布局是必须的,并且需要包含html、body标签
-
创建嵌套路由
- 嵌套路由是由多个 URL 段组成的路由。例如,
/blog/[slug]
路由由三个段组成:/
(根段)blog
(段)[slug]
(叶段)
- 嵌套路由是由多个 URL 段组成的路由。例如,
-
[slug]这种将文件夹名称用中括号括起来,会创建一个特殊的动态路由段,用于从数据生成多个页面,比如产品详情 /product/123. 123是产品id
- 嵌套布局:还可以给文件夹下添加新的布局文件,比如blog下添加layout.js
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return <section>{children}</section>
}
- app/blog/layout.js 接受children
- 文件之间的链接
- 可以使用
<Link href>
跳转 - 也可以使用
useRouter
钩子
- 可以使用
5. 优化图片和字体
-
优化图片 : Next.js 的
<Image>
组件扩展了原始img标签元素,从nnext/image
中导入。提供以下功能-
尺寸优化: 自动为每个设备提供正确大小的图片,使用现代图片格式如 WebP。
-
视觉稳定性: 在图片加载时自动防止布局偏移。
-
更快的页面加载: 使用浏览器原生懒加载功能,仅在图片进入视口时加载,可选模糊占位符。
-
资源灵活性: 按需调整图片大小,甚至包括存储在远程服务器上的图片。
-
src 如果是
本地图片
"/"开头直接能找到 和app同级的public下。Next.js 将根据导入的文件自动确定图片的固有width
和height
。这些值用于确定图片比例并防止图片加载时的累积布局偏移。 -
src 如果是
远程图片
需要手动指定宽和高。还要安全地允许来自远程服务器的图片,你需要在next.config.js
中定义支持的 URL 模式列表。尽可能具体,以防止恶意使用。
-
-
优化字体
next/font
模块自动优化你的字体并移除外部网络请求,以提高隐私性和性能。
6. CSS
- CSS Modules
- blog/style.module.css 要使用css modules 创建一个扩展名为 .module.css的文件并将其导入app下的任何文件夹中。CSS Modules 通过生成唯一的类名来局部作用域化 CSS。这允许你在不同文件中使用相同的类名而不用担心命名冲突。
- 全局CSS : app/global.css 并在根布局中导入
7. 获取数据
- client Components
-
- React 的
use
hook
- React 的
-
- 社区库,如 SWR 或 React Query
-
- 流式传输 :
-
why :在 Server Components 中使用
async/await
时,Next.js 将选择动态渲染。这意味着数据将在服务器上为每个用户请求获取和渲染。如果有任何慢速数据请求,整个路由将被阻止渲染。 -
为了改善初始加载时间和用户体验,你可以使用流式传输将页面的 HTML 分解成更小的块,并从服务器到客户端逐步发送这些块。
-
有两种方式可以在你的应用程序中实现流式传输:
export default function Loading() { // 在这里定义 Loading UI return <div>Loading...</div> }
-
在导航时,用户将立即看到布局和加载状态,同时页面正在渲染。一旦渲染完成,新内容将自动替换。
- 使用 React 的
<Suspense>
组件- 允许更加精细地控制页面的哪些部分进行流式传输 。。例如,你可以立即显示
<Suspense>
边界外的任何页面内容,并在边界内流式传输博客文章列表。
- 允许更加精细地控制页面的哪些部分进行流式传输 。。例如,你可以立即显示
-
-
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
export default function BlogPage() {
return (
<div>
{/* 这些内容将立即发送到客户端 */}
<header>
<h1>欢迎访问博客</h1>
<p>阅读下面的最新文章。</p>
</header>
<main>
{/* 任何包裹在 <Suspense> 边界中的内容都将被流式传输 */}
<Suspense fallback={<BlogListSkeleton />}>
<BlogList />
</Suspense>
</main>
</div>
)
}
8. 更新数据
- 创建Server Functions
- 使用
"use server"
指令创建Server Functions 。你可以将该指令放在异步函数的顶部以将该函数标记为 Server Function,或者放在单独文件的顶部以标记该文件的所有导出。
- 使用
// app/lib/actions.ts
export async function createPost(formData: FormData) {
'use server'
const title = formData.get('title')
const content = formData.get('content')
// 更新数据
// 重新验证缓存
}
export async function deletePost(formData: FormData) {
'use server'
const id = formData.get('id')
// 更新数据
// 重新验证缓存
}
- Server Components
- 可以通过在函数体顶部添加
"use server"
指令来在 Server Components 中内联 Server Functions:
- 可以通过在函数体顶部添加
export default function Page() {
// Server Action
async function createPost(formData: FormData) {
'use server'
// ...
}
return <></>
}
- client components : 在client component中无法定义server function .但是,你可以通过从顶部有
"use server"
指令的文件中导入来在 Client Components 中调用它们:
// app/action.ts
'use server'
export async function createPost() {}
// app/ui/button.tsx
'use client'
// 在client component 中调用 server component
import { createPost } from '@/app/actions'
export function Button() {
return <button formAction={createPost}>Create</button>
}
- 调用server functions 主要有两种方式
-
- Server 和 Client Components中的表单
-
- Client Components 中的事件处理程序
-
- 表单
- React 扩展了 元素,允许通过html中的action属性调用Server fanction.在表单中调用时,函数会自动接收
FormData
对象。你可以使用原生FormData
方法来提取数据:
- React 扩展了 元素,允许通过html中的action属性调用Server fanction.在表单中调用时,函数会自动接收
// form.tsx
import { createPost } from '@/app/actions'
export function Form() {
return (
<form action={createPost}>
<input type="text" name="title" />
<input type="text" name="content" />
<button type="submit">Create</button>
</form>
)
}
// app/action.ts
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
// Update data
// Revalidate cache
}
-
事件处理程序
- 可以通过onclick等事件处理程序,在client component中调用server Function
-
示例
1. 显示等待状态
* 在执行server function时,可以使用react的`useActionState`钩子显示加载指令器。这个钩子返回一个pending的布尔值
// app/ui/button.tsx import { useActionState } from 'react' import { createPost } from '@/app/actions' import { LoadingSpinner } from '@/app/ui/loading-spinner' export default Btton(){ const [state,action,pending] = useActionState(createPost,false);// createPost是处理请求的server function return ( <button obClick={async ()=>action()}> {pending?<LoadingSpinner /> : 'Create Post'} </button> ) }
useActionState 是一个用来专门处理异步提交表单的钩子。它可以让你处理表单提交的状态,比如 loading 状态、返回值、错误信息等。
- 使用场景?
- 你想处理一个表单,比如用户点击提交按钮,后台执行一个异步请求(例如发送 API)。
- 提交后你希望能拿到返回的结果或错误,并根据这个状态更新 UI。
- 而且你不想用
useState
+useEffect
自己组合那么多状态了。
const [state, action, isPending] = useActionState(handler, initialState)
- 参数说明:
参数 含义 handler
一个异步函数,处理提交逻辑 initialState
初始状态(比如:{ success: '', error: '' }) state
当前返回的状态(每次调用 handler 后会更新) action
传给 <form action={action}>
的函数isPending
是否正在执行异步逻辑(loading 状态) 2. 重新验证缓存
- 执行更新后,你可以通过在 Server Function 中调用
revalidatePath
或revalidateTag
来重新验证 Next.js 缓存并显示更新后的数据
import {revalidatePath} from 'next/cache'; export async function createPost(formData:FormData){ 'use server' // Update data // ... revalidatePath('/posts') }
- revalidatePath 和 revalidateTag 是用来刷新(重新生成)页面缓存的工具。让页面数据重新加载,再一次获取最新内容。
-
使用场景:
- 你有个页面展示文章列表,数据来自数据库;
- 页面被访问时,Next.js 会缓存这个页面(默认是静态生成);
- 你在后台添加了一篇新文章;
- 但前台页面不会自动更新!
这时候你就需要用revalidatePath
或revalidateTag
去「手动刷新」页面缓存!
-
revalidatePath 使用方法:刷新某个路径的缓存
import {revalidatePath} from 'next/cache' revalidatePath('/blog') // 刷新blog列表的数据
常用于:
- 提交表单或请求后,让某个页面立即更新
- 你知道具体页面路径(比如
/product/123
)
-
revalidateTag 用法:刷新某个 tag 对应的缓存页面
适用于:import {revalidateTag} from 'next/cache'; revalidateTag('blog-list') // 需要配合fetch使用 // 这个 fetch 的返回会被打上 tag await fetch('https://api.example.com/posts', { next: { tags: ['blog-list'] } })
- 多个页面用了相同的数据源
- 想一次刷新它们的缓存
-
3. 重定向 - 在执行更新后,想要重定向到另一个页面。可以使用server function 中的
redirect
来实现。 - 使用场景?
import {redirect} from 'next/nevigation';
export async function createPost(formData:FormData){
// update data
redirect('/posts')
}
9. 错误处理
- 分为
预期错误
和未捕获异常
- 处理预期错误
- 预期错误是那些可能在应用程序正常运行期间发生的错误,例如来自服务器端表单验证或失败的请求。这些错误应该被明确处理并返回给客户端。
- 服务器函数 : 可以使用
useActionState
hook来处理服务器函数中的预期错误
'use client' import { useActionState } from 'react' import { createPost } from '@/app/actions' const initialState = { message: '', } export function Form() { const [state, formAction, pending] = useActionState(createPost, initialState) return ( <form action={formAction}> <label htmlFor="title">Title</label> <input type="text" id="title" name="title" required /> <label htmlFor="content">Content</label> <textarea id="content" name="content" required /> {state?.message && <p aria-live="polite">{state.message}</p>} <button disabled={pending}>Create Post</button> </form> ) }
- 服务器组件 :
在 Server Component 内部获取数据时,你可以使用响应来有条件地渲染错误消息或
redirect
。
```js
// app/page.tsx
import { getPostBySlug } from '@/lib/posts'
export default async function Page({ params }: { params: { slug: string } }) {
const { slug } = await params
const post = getPostBySlug(slug)
if (!post) {
notFound()
}
return <div>{post.title}</div>
}
// app/blog/[slug]/not-found.tsx
export default function NotFound() {
return <div>404 - 页面未找到</div>
}
```
-
处理未捕获异常 : 意外错误,表明在应用程序正常流程中不应该发生的错误或问题。这些应该通过抛出错误来处理,然后由错误边界捕获。
- 嵌套的错误边界
<ErrorBoundary fallback={<Error/>}></ErrorBoundery>
- Nextjs 使用错误边界来处理未捕获异常,错误边界会捕获其子组件的错误,并显示一个备用UI,而不是崩溃的组件树
// app/dashboard/error.tsx 'use client' // 错误边界必须是客户端组件 import { useEffect } from 'react' export default function Error({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { useEffect(() => { // 将错误记录到错误报告服务 console.error(error) }, [error]) return ( <div> <h2>出错了!</h2> <button onClick={ // 尝试通过重新渲染段来恢复 () => reset() } > 重试 </button> </div> ) }
- 嵌套的错误边界
-
错误将冒泡到最近的父错误边界。这允许通过在路由层次结构的不同级别放置 error.tsx文件来进行细粒度的错误处理
- 全局错误
- 虽然不太常见,但你可以使用
global-error.js
文件(位于根 app 目录中)来处理根布局中的错误,即使使用了国际化。全局错误 UI 必须定义自己的<html>
和<body>
标签,因为在激活时它会替换根布局或模板。 ```js // app/golobal-error.js 'use client' // 错误边界必须是客户端组件
- 虽然不太常见,但你可以使用
export default function GlobalError({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { return ( // global-error 必须包含 html 和 body 标签
出错了!
<button onClick={() => reset()}>重试 ) } - 全局错误
```
10. 添加元数据和创建 OG 图片
- Metadata API 可用于定义你的应用程序元数据,以改善 SEO 和网络分享效果,包括:
- 静态
metadata
对象 - 动态
generateMetadata
函数 - 特殊文件约定,可用于添加静态或动态生成的网站图标和 OG 图片。
- 静态
- 静态元数据
- 从静态layout.js 或者 page.js文件中到处一个MetaData对象
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Blog',
description: '...',
}
export default function Page() {}