现在主流的前端框架大多用于创建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
,streaming
andserver 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/dashboard
Files
用于定义路由对应的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.ts
Server-side API endpointtemplate.tsx
特殊的每次都重复渲染的Layout UIdefault.tsx
Fallback 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 component
server 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 component
route handler
server 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应用程序。