现在主流的前端框架大多用于创建SPA应用,SPA的缺点是首屏等待时间长和SEO不友好,对于面向个人消费者的应用来说,这很影响体验。因而前后端同构的SSR方案应运而生,但是自己搭建SSR项目却比较费劲,Next.js是一个基于react的SSR框架,能够帮助我们快速搭建SSR项目.
希望本文能让大家对Next.js有一个全面的了解,看完后有所收获 :)
简介
Next.js 是一个基于react的SSR框架, 用于构建全栈web应用. 开发者只需专注于用react构建UI, Next.js 帮助我们做优化(SEO / 图片加载 / 页面性能等), 并且提供了很多其他功能.
Next.js使用webpack作为构建工具, 并且默认配置好了TS、Eslint和tailwind.css.
特点
- 基于file-system的路由
- 客户端和服务端渲染
- 扩展fetch API, 简化数据请求
- 支持很多样式方案, 如: css module, tailwind css and css-in-js, sass
- 优化图片和脚本加载
- 全面支持TS
安装
创建next.js项目
1 | |
路由
支持两种路由方式: app router 和 pages router
app router 支持最新的react特性, 如
server component,streamingandserver actions
项目结构示例:

在 Next.js version 13, 引入了全新的App Router,它是基于React Server Components实现的.
App Router 会把所有的代码都放在名为app文件夹中. app 文件夹可以和 pages 文件夹共同存在,允许我们将旧项目逐步地切换到新的 App Router。

默认地出于性能优化的考虑, app文件夹下的组件都是 Server Components,不过我们也可以在文件开头声明use client把组件变为Client Components.
文件夹和文件的作用
Folders用于定义路由. 如:app/dashboardFiles用于定义路由对应的UI, 如app/dashboard/layout.tsx和app/dashboard/page.tsx

page.js 可以让当前文件夹被识别为路由,即可被公开访问

在这个例子中, /dashboard/analytics URL 不是可以公开访问的,因为它不包含page.js.
Route Segments
每个文件夹代表一个 route segment. 每个 route segment 又对应 URL Path的一个 segment

嵌套路由
{folder}/{subfolder}/page.tsx定义页面 (page handler){folder}/{subfolder}/route.ts定义接口 (api handler)


动态路由
[folder]动态路由[...folder]catch-all 动态路由[[...folder]]optional catch-all 动态路由
分组路由
(folder) 带括号的文件夹用于分组,对路由路径没有影响,在同一个分组下的页面可以共享一个layout, 可用分组路由根据业务模块组织代码文件

私有文件夹
_folder 带有下划线的文件夹, 里面的文件会被路由系统忽略,不会被识别为路由(即使包含 page.tsx), 只能被其他文件引用。
并列路由
@folder (文件夹名为slot名) 定义并列路由, 可以在同一个layout下渲染多个页面

拦截路由
(.)folder (..)folder (..)(..)folder (...)floder 拦截路由是指在当前页面通过<Link>跳转时, 若目标页面有对应的拦截路由,则会渲染该拦截路由下的page.


特殊文件名约定
Next.js 提供了一组特殊文件去创建特殊组件,然后组织嵌套在一起,得到最终的UI
layout.tsx布局组件,定义它下面pages共享的UIpage.tsx页面组件,使当前文件路径可作为路由,被公开访问loading.tsx当前路由下的Loading组件not-found.tsx当前路由的Not found组件error.tsx当前路由的Error组件global-error.tsx全局的Error组件route.tsServer-side API endpointtemplate.tsx特殊的每次都重复渲染的Layout UIdefault.tsxFallback UI for Parallel Routes
特殊组件嵌套方式
特殊组件都是当前路由所对应页面UI的一部分,它们交织形成完整的页面。

在嵌套路由中, 每个层级的route segment对应的特殊组件树,也会嵌套形成更大更深的组件树

其他文件组织方式
我们可以把components, styles, tests等文件夹直接放在app目录下,这样它们就可以被其他文件引用了。因为它们不包含 page.tsx 或 route.ts,所以它们不会被Next.js识别为路由。
同样,识别为路由的文件夹下面,也可以放 components, styles, tests等文件夹,这样它们就可以被当前路由下的页面引用了。
1 | |
这是因为当文件夹被识别为路由时,只有page.tsx或route.ts返回的内容,才是会被用户访问到的。如上例中,Modal.tsx的内容,不会被用户访问到,但是可以被product路由下的页面引用。

Pages
page.tsx 是对应当前route segment的页面组件

1 | |
- 为了让文件夹被识别为页面路由,
page.js文件是必须的。 - Pages 默认是
Server Components, 不过也可声明为Client Component. - Pages 可以fetch data.
Layouts
layout是跨route共享的UI,在导航时,layout会保持状态,保持交互性,不会重新渲染。layout也可以嵌套。
举个例子,以下的layout会被 /dashboard 和 /dashboard/settings 共享

1 | |
Root Layout
root layout 是必须的,它位于app/layout.tsx, 不同于其他层级的layout, route layout必须包含 html 和 body 标签, 允许我们定义初始返回给浏览器的html内容。
1 | |
Nesting Layouts
layout是可以嵌套的,parent layout通过 children prop包裹child layout。


当文件夹同时包含 layout.js 和 page.js 文件时,按照前面所说的特殊文件组织方式,layout会包裹page.
Layouts 可以 fetch data.
parent layout 和 child layout之间传递数据是不可能的,但是可以直接fetch相同的接口获取数据,fetch API会复用缓存数据, 避免性能影响
可以利用分组路由Route Groups 把需要相同布局的pages组织在一起,另外还可以利用Route Groups创建多个 root layouts.
Templates
Templates 类似 layouts, 不同的地方是templates在导航时为每个子路由创建一个新的实例。

1 | |
Metadata
若需要修改 <head> HTML elements,可以使用 Metadata APIs。
Metadata APIs 可以在 page.js 或 layout.js 文件中定义。
导出 metadata 对象或 generateMetadata 函数来定义 metadata。
1 | |
generateMetadata中可以发起数据请求,并且可以利用params参数获取当前路由的参数。
1 | |
渲染
Server component
server component 应当声明为 async function, 因为通常都需要请求数据,然后通过props传递给client componentserver component 不能包含交互,即不可进行DOM事件监听server component 在后端渲染后,会被缓存,提高再次请求的响应速度。
渲染任务会根据 route segment 和<Suspense> boundaries进行分割,并且通过流的方式发送给客户端,以减少等待时间。
组件树通常会是server component和client component的互相交织,server component会被优先渲染执行。
服务端组件的内容, 也称为 React Server Component Payload, 它包含:
server component的渲染得到的虚拟Domclient component的占位元素和引用server component传给client component的props
渲染过程:
- 根据服务端返回的html,渲染一个不可交互的页面
- 获取路由对应的服务端组件的内容(
React Server Component Payload) 用来调和客户端和服务端组件树,更新DOM. - 执行hydration, 使页面可以交互
Client component
client component 不可以声明为 async function, 否则会报错
作为入口路由的一部分时,client components也会在服务端执行。
client component 可以使用 useEffect 和 useState 等React hooks,绑定DOM事件,调用浏览器API.
如果一个组件通过use client声明为客户端组件,那么它的子孙组件都会默认为客户端组件,除非显式声明为服务端组件。
默认地,被识别为路由的文件夹下的layout和page会并行渲染。
渲染类型:
static(静态渲染) : 在构建时渲染,适用于静态页面,如博客文章,不会频繁更新。dynamic(动态渲染) : 在请求时渲染,适用于需要频繁更新的页面,如用户个人主页,购物车等。
Nextjs会自动选择使用static rendering还是dynamic rendering, 如果页面使用到dynamic functions那么就会采用动态渲染。
dynamic functions是指:
cookies()headers()props.searchParams
常用组件开发模式:server component fetch data, 通过props传递data给 client component
Next.js应用本质上就是一个包含服务端组件和客户端组件的组件树,当其中一个组件通过use client声明为客户端组件时,它就形成了一个client subtree
client subtrees 也可以包含 server components 或者调用 server actions
1 | |
在收到请求时,Next.js会先渲染server components,然后返回一个包含server components渲染结果的RSC payload,这个payload会包含client subtree的引用,在客户端,React会使用RSC payload来协调client subtree。
既然 client component 的渲染是在 server component的渲染之后,那么就不能在 client component 中导入 server component,因为那会导致一个新的请求回传到服务器,应该通过props将 server component 传递给 client component。
1 | |
数据请求
Server action
server action 可以在 client component 中使用, 会发送ajax请求给对应的路由,返回后端数据,可以隐藏真实API,可通过 <form action> 或 element onClick callback触发。
服务端请求
在 server component 中,可以使用 fetch 函数来发送请求,获取数据。
全页面刷新时,server component 会重新执行,获取最新的数据。
服务端请求的优点有:
- 减少请求数量
- 保护敏感数据
- 离数据源更近,更快获得数据
- 可缓存,提高性能
可以使用fetch API在服务端请求的地方:
server componentroute handlerserver actions
由于fetch会缓存数据,所以在服务端组件之间不需要使用单向数据流模式,通过props传递数据。直接在每个服务端组件fetch相同的接口即可,接口只会被请求一次。
server component 不需要通过fetch方法调用 route handler,它可以直接访问数据库
若用<Suspense>包裹组件,则组件会动态渲染,作为入口路由进行全页面渲染时不会包含该动态组件。
1 | |
fetch缓存
fetch 设置缓存语法: fetch(api, { cahce: 'force-cache' })
fetch缓存有效性验证:
- time-based
fetch(api, {next: { revalidate: 3600 }}) - tag-based & path-based
1
2
3
4
5
6
7
8import { revalidatePath, revalidateTag } from 'next/cache'
export async function createPost() {
revalidatePath('/posts')
}
fetch(api, { next: {tags: ['haha'] }})
revalidateTag('haha')
可以把获取数据的方法定义在page组件外部,然后在page组件中调用,这样就可以在多个组件中复用获取数据的方法。
1 | |
客户端请求
客户端请求数据适用于这些场景:
- 部分渲染,部分UI仅在客户端渲染,这部分UI所包含的数据只能从客户端发请求获得
- 实时数据,如:搜索结果
Server action
server actions and mutations
server actions 是运行在服务端的 async function, 它可以在 server component 和 client component 被调用
声明server actions:
'user server'指令,放在函数声明之前1
2
3
4
5
6
7
8
9export default function Page() {
// server action
async function create() {
'user server'
// todo
}
return (<div>hi</div>)
}'user server'指令, 放在代码文件顶部
1 | |
在client component中,使用server action
1 | |
Nextjs扩展了 <form> 元素,允许它的 action 属性接收server action
useActionState hook, 可以获取server action的执行状态
1 | |
server action 应当被看做一个公开的接口,不过这个接口的地址是一些没有语义的随机字符
常见问题
如何在layout中访问请求对象?
出于在页面间导航时重用layout的目的,layout.tsx 不能访问原始的request对象。但是,你可以使用headers()和cookies()方法来访问相对的请求信息。
如何访问页面的URL?
page默认是server component, 所以无法直接访问URL, 可以使用usePathname和useSearchParams来获取URL, 另外page的props中也有params和searchParams属性, 可以直接访问.
Server component中怎样重定向到其他页面?
在server component中, 可以使用redirect()或permanentRedirect()方法来重定向到其他页面.
怎样设置cookies?
可以在Server Actions, Middleware or Route Handlers使用cookies()方法来设置cookies.
You can set cookies in Server Actions or Route Handlers using the cookies function.
注意: 我们不能在page或layout中直接设置cookies, 因为HTTP不允许在流式传输开始后设置cookies。
总结
Next.js是一个强大的React框架,它提供了许多功能,如静态网站生成、服务器端渲染和API路由。通过使用Next.js,我们可以轻松地构建高性能的Web应用程序。