47-Rendering

image-20250912104448020

image-20250912104513825

这一节课主要是介绍一下rendering是什么意思,接下来会具体讲解。

48-Client side Rendering(CSR)

这节课主要讲CSR的概念和优缺点,主要说明了CSR有哪些缺点,由此就带出下一节的SSR。

image-20250912105202158

下面是SPA的渲染过程,重点就是html文件里面的一个<div id="root"></div>,和引入的js文件:

image-20250912105744327

 

SPA很流行,但是有一些缺陷。

image-20250912105359367

image-20250912105435938

image-20250912105512463

image-20250912105524878

49-Server-side Rendering(SSR)

这节课主要讲SSR的概念和优缺点,主要说明了SSR的缺点,由此带出下一节的Suspense SSR的概念。

image-20250912110149362

SSR是怎么做的?

when a request comes in, instead of sending a bare html file that needs client-side javascript to build the page, the server now handles rendering the component html.

this quickly formed html document goes straight to the browser.

当请求进来时,服务器现在负责渲染完整的html页面,而不是发送一个需要客户端javascript来构建页面的裸html文件。快速形成的html文档直接发送到浏览器。

image-20250912111607731

since the html is already generated in the server, the browser can quickly parse and display it. Give us faster initial page load time.

image-20250912112329039

SSR有以上好处,但是也有缺陷:

image-20250912112533923

html页面虽然渲染很快,但是还有一个js文件,只有当js文件加载完成之后,并将js代码注入到html之后,用户才能操作交互。这个注入的过程叫做:hydration。hydration本意是水合作用,可以把这个过程看成是一些干货,需要水发之后才能成为美味。

image-20250912112938735

hydration的作用如下:

image-20250912113026101

服务端的解决方案有两种:SSG和SSR,但这两种经常被统称为SSR。

image-20250912110445294

SSR的缺点:

image-20250912110528650

 

image-20250912110557788

 

image-20250912110629340

上面的三个缺点,就造成了SSR的这些现象:

image-20250912110707847

50-Suspense SSR

 

image-20250912113549750

为了解决上面的问题,react18引入了suspense SSR architecture。

image-20250912113700693

使用<Suspense>可以达到上面的两种效果。

传统的SSR是这样渲染的:

image-20250912113838685

以下是一段说明:

 

简要的说就是:使用<Suspense>包裹住渲染比较慢的部分,那么react不会等待这部分渲染完成,而是以stream的方式渲染其余部分,这一部分就会显示一个spinning样式。当这一部分的数据准备好时,react streams the additional html through the ongoing stream along with some javascript that does exactly to the html.

image-20250912143233353

但还存在一个问题,只有js加载完了之后用户才能开始交互,如果js部分很大,用户还是需要等待。

image-20250912143437492

此时可以使用React.lazy来将main section里面的代码与其它部分的代码隔离开来。

image-20250912143516152

使用了React.lazy,这样就可以做到选择性的hydration。

image-20250912143946983

这是lazy和suspense共同起作用之后的效果:

 

image-20250912144327487

selective hydration同时解决了之前SSR遇到的第三个问题:hydrate the entire page to interact。

1、react会先hydrate那些没有使用React.lazy的组件。

2、如果处于awaiting hydration期间,react会根据用户的交互优先hydrate有交互的组件。

image-20250912144425576

以下是流程说明:

react18的new features解决了传统SSR的三个问题。

image-20250912145152816

但是Suspense SSR还是存在3个问题:

1、用户最终都要下载整个项目的代码,代码量其实还变大了。

image-20250912145252784

2、不必要的hydration会延迟交互

image-20250912145326915

3、我们没有利用好server,最终还是让用户的设备承担起了大部分js工作

image-20250912145422243

51-React Server Components

Suspense SSR还是存在问题,所以react为我们提供了 RSC ,来解决。

image-20250912150343775

RSC引入了双组件模型 dual-component model,看上去就很熟悉了,client component和server component。

image-20250912150413816

client component和server component并不是它们的功能来区分的,而是基于它们的执行环境和它们设计用来交互的具体系统。

image-20250912150520443

1、client components

首先说一下client components,就是我们很熟悉的react components。

image-20250912150919780

这里有一句话:but they can also be rendered to HTML on the server(SSR)。这句话该怎么理解?不是说它们变为了server components,而是指react给我们的一种优化策略,client components能够,同时也应该在server上运行一次以获取更好的性能,这部分应该是react框架帮助我们做的。

image-20250912150937964

client components能够使用client environment的所有功能。

image-20250912151031802

2、server components

image-20250912151701105

image-20250912151748520

image-20250912151807784

server components有以下8个好处:

1、打包体积更小

image-20250912151916375

2、直接连接服务端的资源

image-20250912151949201

3、增强了安全性

image-20250912152009729

4、提高数据请求性能

image-20250912152035033

5、缓存

image-20250912152102851

6、更快的初始化page加载速度,更快的内容绘制

image-20250912152126895

7、SEO更好

image-20250912152146458

8、有效的流

image-20250912152212616

最后总结一下server component:

image-20250912152257851

image-20250912152322621

最后总结以下RSC:

image-20250912152412801

RSC和nextjs是什么关系呢?

nextjs是建立在RSC architecture之上的,所以RSC的好处,使用nextjs就可以全部得到了。

image-20250912152502669

 

真的是需要感谢老师,帮忙把整个react的演变过程讲解清楚了。我之前学习了react route v6,里面见到过这种写法,就是在server components里面进行data fetching,但就是不知道为什么要这么做?怎么做的好处在哪里?

这几节课要多看几遍,直到自己能够复述出来。

52-Server and Client Components

用案例来学习server components和client components,新建一个项目npx create-next-app rendering-demo

1、server components

nextjs中所有的组件默认都是server component。

新建一个app/about/page.tsx

访问这个页面,查看console输出的内容,可以看到前面有一个server的图标,说明这个component是server component。

image-20250912154912766

server components can't maintain state, because they run on the server, where browser-based state management doesn't exist.

服务端组件无法维护状态,因为它们在服务器上运行,在哪里基于浏览器的状态管理并不存在。

2、client components

现在app/page.tsx里面新建一个Link链接。这里不仅仅使用url来测试,还使用链接来做测试,因为链接是有作用的,后面可以看到。

image-20250912175929057

新建一个app/dashboard/page.tsx,这个组件使用了useState

"use client";的作用是什么呢?

image-20250912155320250

这里老师说了一句:the "use client" directive signals to nextjs that this dashboard component along with any components it imports, is intended for client-side execution.

重点是这里的along with any components it imports,就是说一个组件变成了client component,那么它里面引用的所有组件都会在client端进行渲染执行。

可以看到,在client components里面,useState是可以使用的。

a very import point about client components rendering behavior

app/dashboard/page.tsx组件中,输出一些信息。

image-20250912181324536

从首页的Link进入dashboard页面:

可以看到有两次输出,并且输出没有带server的标签。查看terminal里面,没有输出。

为什么有两次输出呢?因为nextjs使用了严格模式,在开发环境是有两次输出。

现在reload the page,会发现浏览器的console面板里面有两次输出,并且terminal里面也有输出:

为什么在terminal里面会有输出呢?

因为当我们使用Link来导航时,dashboard component只在client-side进行渲染,我们可以从浏览器console面板看到。

当我们reload page时,the dashboard component is rendered once on the server to allow the user to immediately see the page's HTML content rather than a blank screen.

 

image-20250912160839115

53-RSC Rendering Lifecycle

image-20250913091646526

下面,老师为什么讲解了RSC loading sequence(RSC loading 序列)以及RSC update sequence(RSC 更新序列),我分为几个gif录制了,效果不好就看视频。

 

 

 

在nextjs中,there are three different ways rendering can happen on the server:

image-20250913092643370

54-Static Rendering

image-20250913092826438

static rendering有这么多好处,那么我们在nextjs里面应该怎么做才能实现呢?

答案是不需要做任何事情,因为static rendering 是app router的默认策略。

image-20250913092918963

但是又引出了一个问题:我们在开发模式下,难道这种策略也能生效吗?这个不是发布到服务器上才能做到吗?

image-20250913093318786

答案是这样的:运行npm run dev,你会看到项目里面的一个.next目录,真正运行的就是这个目录所在的项目。在开发环境,每次请求page的时候,都会执行pre-rendered。

app/page.tsx里面添加一个<Link href="/about">About</Link>标签,并且在about/page.tsx里面添加显示当前时间:

将rendering-demo的.next文件夹删除,运行npm run build。你会看到这些:

image-20250913095333428

这里给出了加载相应的页面所需要的JS文件的体积。同时在每个route的前面有一个空心圆圈,这表示static rendering,they are all pre-rendered at build time as static HTML.

再来看.next文件夹,重点关注serverstatic文件夹,

image-20250913095615449

在server文件夹内,有一个app文件夹,which matches our application's route structure.

运行npm run start,清除浏览器缓存再访问:

image-20250913101956399

清除network里面的内容,访问/about/dashboard,可以看到没有任何请求发生,这就是static rendering起作用了。

可以看到时间被固定在打包的时候了,无论怎么刷新都没有效果。这种特性需要记住。

这里有一个疑问,就是我们访问的是/这个路由,为什么此时会加载/about/dashboard里面的内容呢?这是因为nextjs用到了prefetching的技术。

image-20250913094854101

这节课内容非常丰富,只能多看几遍视频来理解,重点是讲解了nextjs是怎么实现static rendering的。

image-20250913095032420

55-Dynamic Rendering

image-20250913102653910

问题:我们怎么告诉nextjs去dynamic render哪些页面呢?

答案是当nextjs探测到dynamic function或者dynamic API时,nextjs会自动切换到dynamic rendering。

image-20250913102816387

以案例来说明:

app/about/page.tsx文件里面使用cookie,来看是否会触发nextjs的dynamic rendering。

清除.next文件夹,运行npm run build,查看输出:

image-20250913103538579

可以看到/about前面是一个f,这个意思下面给出了,这是动态渲染的。接着我们查看.next/server/app文件夹里面,并没有about.html文件,说明这个文件不是在build的时候创建的,而是会动态渲染。

image-20250913103655222

运行npm run start,可以看到不管是page reload还是使用Link来访问/about,都会有数据返回,并且terminal里面也会有输出,这就是dynamic rendering。

下面是dynamic rendering的总结:

image-20250913103941670

如果不使用dynamic functions或者dynamic API,也可以将一个route变为dynamic rendering。方法就是在page页面上添加export const dynamic = "force-dynamic";

image-20250913104006157

56-generateStaticParams

这一节的案例是说:

新建一个products页面展示productList,然后动态渲染productDetail,此时会用到[id]/page.tsx,此时的详情页面都是dynamic rendering。

由于productList里面固定有3个product,我想把这3个的详情页面做成static rendering,性能会更好一些,该怎么做?这其实有实际例子的,比如说网站里面一些固定的内容、固定的头条之类的。

下面是实现的方法,用generateStaticParams()来实现。

image-20250913105033440

新建app/products/page.tsxapp/products/[id]/page.tsx

运行npm run buildnpm run start,注意看每次进入详情界面的时候,都会动态渲染:

image-20250913112850569

但是product1、2、3是固定的,我想把这三个做成static rendering。需要使用generateStaticParams指定具体哪些参数值。

重新打包:

image-20250913112223142

注意看出现了一种新类型SSG。这就是生成的静态渲染文件。

重新运行看一下:

可以看到product1、2、3是静态渲染,page reload之后,时间也不会改变。

我这里有一个问题了,如果参数是4、5、6等等别的数字呢?会怎么样?下一节课会学习到,nextjs在运行时静态渲染它们。

 

那么如果是多个参数呢?首先路由结构必须是这样的/products/[categoryId]/[productId]/page.tsx,其次generateStaticParams里面的数组对象,参数顺序要跟定义路由时保持一致。

image-20250913105528004

57-dynamicParams

上节课我们学习了generateStaticParams,并且制定了id为1、2、3时为静态渲染。那些没有指定的id值呢?比如说4、5等等,nextjs statically renders them at runtime。nextjs在运行时静态渲染它们。

这是什么意思呢?注意看我们打包后的.next/server/app/products,里面只有1、2、3的html页面。

image-20250913113947543

当我们访问/products/4之后,这里面会生成一个4.html文件。并且刷新浏览器,时间是不变的。以此类推。

原因如下:

image-20250913115246889

generateStaticParams()方法在使用时,nextjs默认设置dynamicParams为true,表示允许没有在generateStaticParams中列出的参数访问时,生成静态渲染文件,就像上面访问/products/4时发生的那样。

那么如果dynamicParams设置为false,那么访问没有在generateStaticParams中列出的参数访问时,nextjs会生成404页面返回。

试一下,删除.next,重新打包运行:

可以看到返回404页面。

 

那这个参数在哪些情况下设置为true,哪些情况下设置为false呢?

image-20250913120004583

image-20250913120026757

58-Streaming

image-20250913120104666

image-20250913120117785

image-20250913120131240

nextjs的app route里面集成了streaming这种渲染方式,需要使用<Suspense>标签。

image-20250913120202505

创建/app/product-review/page.tsx,里面引入两个组件,这两个组件分别使用了2秒和4秒的延迟,这样来看效果。

运行看一下:

可以看到延迟了4秒多钟之后才加载完成。

we wrap the slow components with suspense, and nextjs handles the rest.我们只需要将加载慢的组件用<Suspense>标签包裹起来,nextjs就会使用streaming strategy来加载组件。

查看效果:

可以看到没有使用suspense包裹的组件先显示,使用suspense包裹的组件会先显示fallback的内容,加载完成后会显示完整内容。

59-Server and Client Composition Patterns

下面是server components和client components使用的一些场景:

image-20250913150944469

image-20250913151009848

60-Server-only Code

server only和client ony只是说一些代码最好只在server component或者client component里面使用,这些代码如果被错误使用,会造成泄密或者报错。

重点就是怎么防止程序员不小心用错。就是在编写阶段引入server-only或者client-only库,使用它们,然后为我们探测到,就可以提示我们。

image-20250913151203787

一些js代码只需要在server上运行,如果不小心泄露到了client上面去就不好了,所以使用一个名为server-only的包,npm i server-only

这个包的作用就是:如果有人不小心将这里面的代码引入到client components里面去,在项目构建的时候会报错(之前我们已经知道了,Next在dev的时候,也是有构建的,所以开发的时候也会报错)。

image-20250913151232629

创建app/server-route/page.tsxapp/client-route/page.tsxsrc/utils/server-utils.ts,分别在两个组件里面引入这个工具函数。这个serverSideFunction的本意是只在server上使用,但是这里就是模拟如果不小心将这个函数引入到了client components里面去,能不能正常使用?

可以看到在server component和client component里面,都可以正常使用函数。

但是serverSideFunction这个函数我只想用在server端,怎么做?安装server-only这个库,然后在这个文件里面引入即可。

当发现client component里面使用这个函数的时候,nextjs就会报错:

61-Third Party Packages

react区分了server component和client component,但是大多数第三方包并没有跟上,应该都还是只有client component这一种类型的组件。那么当在nextjs的server components里面引入并使用这些第三方库的组件的时候,会报错。

解决办法是什么呢?将这些第三方组件,放到我们自定义的client component里面去,然后在我们的server components里面使用自定义的client component,这样就不会报错了。

image-20250913154603693

下面以案例说明:

安装npm i react-slick slick-carousel @types/react-slick --force,在app/client-route/page.tsx里面引入轮播图。

查看效果,第三方库的组件可以正常使用。

那么在server component里面可以正常使用吗?不用太复杂,直接将"use client";去掉,就可以模拟这种情况,看一下:

image-20250913160942921

可以看到报错了。

怎么解决呢?将第三方库的代码包裹到自定义的client component里面,然后引用这个自定义的组件即可。

新建src/components/ImageSlider.tsx

然后在server-route/page.tsx里面引用这个组件:

62-Context Providers

这一节课看了老师的案例,我其实有一个疑问,就是provider提供了全局的上下文,但是还是需要使用hook来访问。这和我直接引入一个变量有什么区别呢?

好处如下:

在 Next.js(或更广泛的 React 应用)中使用 Provider(如 React Context 的 Provider、Redux 的 Provider 或其他库的类似机制)有许多好处,尤其是在状态管理、数据共享和应用架构方面。以下是使用 Provider 的主要好处,结合 Next.js 的上下文进行说明:


  1. 全局状态管理

  1. 简化组件间的通信

  1. 支持服务器端渲染(SSR)和静态生成(SSG)

  1. 模块化和可维护性

  1. 支持复杂状态逻辑

  1. 一致的用户体验

  1. 便于测试和调试

  1. 灵活性和扩展性

注意事项


总结

Provider 在 Next.js 中的主要好处包括:

如果你有具体场景(如需要结合某个库或实现某功能),可以提供更多细节,我可以进一步为你定制示例或优化方案!

那么在这节课的案例中就是保持了一致性,比如说用户修改了这个theme.colors变量,那么所有用到provider提供的theme.colors的地方,都会重新渲染。

在 React 中,Provider(如 React Context 的 Provider)通过 Context API 提供状态。当 Provider 的 value 发生变化时,所有通过 useContext 或其他方式(如 useSelector 在 Redux 中)订阅该状态的组件都会重新渲染。

image-20250913161729153

首先尝试在app/layout.tsx里面直接定义providers:

image-20250913164447903

运行报错,原因就是不能在server components里面使用react hooks:

image-20250913164638061

看似可以将这个组件变为client server来解决问题,但是这样会让里面所有的组件都在client-side运行。

解决办法是创建一个provider组件,是client component,用它包裹住app/layout.tsx里面的{children}

app/layout.tsx中引入并使用这个provider。

image-20250913170254173

然后我们在app/client-route/page.tsx里面使用provider提供的值。

查看效果:

image-20250913170538731

这里有一个问题,<ThemeProvider>包裹住了children,那里面的server component会变为client component来渲染吗?答案是不会。server component 依然是 server component,后面的学习中会讨论到。

63-Client only Code

之前我们学习了怎么样让一些代码只在server上能够使用,解决办法是使用server-only这个第三方库。现在我们想让一些代码只在client上能够使用,比如说DOM操作、localStorage操作等,这些在server上运行就会报错。

解决办法还是使用第三方库:client-onlynpm i client-only

image-20250913170908174

以案例说明,我编写了一个clientSideFunction方法,这个方法使用了client-only

在server component里面使用这个函数,就会报错:

image-20250913172547534

image-20250913172627542

64-Client Component Placement

因为server components无法处理状态和交互,所以需要client components来完成。那么client components在项目中的位置应该怎么摆放呢?

建议将client components放在components tree中较低的位置。什么叫“较低的位置”呢?就是层级结构较深的位置。

image-20250913172713597

以案例说明,landing page包括navbar和main两部分,这节课上我们只关注navbar部分。

代码很简单,这个案例主要是为了说明client components应该放在哪里。

image-20250913172926537

navbar里面包含两部分:nav links和nav search。

image-20250913172955329

这是此时的component tree,注意sc表示server component,表示此时这些组件都是server components。

image-20250913173014039

运行一下,可以看到所有的组件都是server components:

image-20250914093220731

现在添加一个状态来关联搜索框,定义在navbar里面:

可以看到,输出内容的前面都没有server的tag,说明这些组件都是client components。

image-20250914093612160

when you mark a component with "use client" directive, it doesn't just affect that component but also affects every child component in the component tree below it.

image-20250914093818161

image-20250914093841056

image-20250914093909419

that's why we want to push client components as far down the tree as possible , ideally making them leaf components.

我们需要将客户端组件尽可能地推到树的深处,理想情况是将它们变为叶子节点上的组件。

在这个案例中,可以将nav-search组件变为client component,navbar依然保持server component。

image-20250914095019632

可以看到,只有nav-search是cient component。

65-Interleaving Server and Client Components

这几课讲解server和client components的交错情况,共讲解了4种情况,看老师的视频讲解即可,代码很简单,这里记录一下结论。

1、一个server component位于另一个server component里面,正常。

2、一个client component位于另一个client component里面,正常。

3、一个client component位于另一个server component里面,正常。

4、一个server component位于另一个client component里面,报错。

第4种情况错误的原因是:any component nested inside the client component automatically becomes a client component.but the server component uses fs module which is not available in the browser.

这里报错的原因还是server component里面使用了nodejs的fs模块,而这个模块在客户端是没有的,所以报错。如果server components里面没有使用任何客户端不支持的东西,那转为client components之后,是不会报错的。但这是碰运气的做法,真正的解决方案如下:

可以将server component当作children传到client component里面使用,这样不会报错,并且不会将server components转变为client components。

这样就解决了第62节课 context provider 的问题,为什么Provider 包裹 children 之后,里面的server components不会变为client components。

下面给出一个例子:

 

image-20250914102119320

可以看到server component依然是server component。

 

总结一下学习过的rendering的相关概念:

image-20250913174255171