从 Next.js 看企业级框架的 SSR 支持

开发 架构
面向生产使用的 React 框架(废话)。提供了好些开箱即用的特性,支持静态渲染/服务端渲染混用、支持 TypeScript、支持打包优化、支持按路由预加载等等。

[[355666]]

本文转载自微信公众号「前端向后」,作者黯羽轻扬  。转载本文请联系前端向后公众号。 

 一.Next.js 简介

The React Framework for Production

面向生产使用的 React 框架(废话)。提供了好些开箱即用的特性,支持静态渲染/服务端渲染混用、支持 TypeScript、支持打包优化、支持按路由预加载等等:

Next.js gives you the best developer experience with all the features you need for production: hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more. No config needed.

其中,完善的静态渲染/服务端渲染支持让 Next.js 在 React 生态中独树一帜

二.核心特性

如果说 Next.js 只做了一件事,那就是预渲染(Pre-rendering):

By default, Next.js pre-renders every page. This means that Next.js generates HTML for each page in advance, instead of having it all done by client-side JavaScript.

具体的,预渲染分为两种方式:

  • SSG(Static Site Generation):也叫 Static Generation,在编译时生成静态 HTML
  • SSR(Server-Side Rendering):也叫 Server Rendering,用户请求到来时动态生成 HTML

与 SSR 相比,Next.js 更推崇的是 SSG,因为其性能优势更大(静态内容可托管至 CDN,性能提升立竿见影)。因此建议优先考虑 SSG,只在 SSG 无法满足的情况下(比如一些无法在编译时静态生成的个性化内容)才考虑 SSR、CSR

P.S.CSR、SSR 等更多渲染模式,见前端渲染模式的探索

围绕核心的预渲染功能,延伸出了一系列相关支持,如:

  • 路由(文件规范、API):多页面的基础
  • 页面级预渲染、代码拆分:顺理成章
  • 增量静态生成:针对大量页面的编译时预渲染(即静态生成)策略
  • 按路由预加载:锦上添花
  • 国际化(结合路由):锦上添花
  • 集成 Serverless 函数:锦上添花
  • 自动 polyfill、自定义head标签:友情赠送

此外,还提供了一些通用场景支持:

  • 开箱即用(0 配置)
  • TypeScript
  • CSS module、Sass
  • Fast Refresh(可靠的 Hot Reload 支持)
  • 用户真实数据收集分析(页面加载性能、体验评分等)
  • 带默认优化的Image组件

三.路由支持

Next.js 提供了两种路由支持,静态路由与动态路由

静态路由

静态路由通过文件规范来约定,pages目录下的js文件都认为是路由(每个静态路由对应一个页面文件),例如:

  1. pages/index.js → / 
  2. pages/blog/index.js → /blog 
  3. pages/blog/first-post.js → /blog/first-post 
  4. pages/dashboard/settings/username.js → /dashboard/settings/username 

动态路由

类似的,动态路由也要在pages目录下创建文件,只是文件名有些不同寻常:

  1. pages/blog/[slug].js → /blog/:slug (/blog/hello-world) 
  2. pages/[username]/settings.js → /:username/settings (/foo/settings) 
  3. pages/post/[...all].js → /post/* (/post/2020/id/title) 

路径中变化的参数通过getStaticPaths来填充:

  1. // pages/posts/[id].js 
  2. export async function getStaticPaths() { 
  3.   return { 
  4.     // 必须叫paths,值必须是数组 
  5.     paths: [{ 
  6.       // 每一项必须是这个形式 
  7.       params: { 
  8.         // 必须含有id 
  9.         id: 'ssg-SSR' 
  10.       } 
  11.     },{ 
  12.       params: { 
  13.         id: 'pre-rendering' 
  14.       } 
  15.     }], 
  16.     fallback: false 
  17.   } 

进一步传递给getStaticProps按参数获取数据,并渲染页面:

  1.  props: { 
  2.       postData 
  3.     } 
  4.   } 
  5.  
  6. // 渲染页面 
  7. export default function Post({ postData }) { 
  8.   return ( 
  9.     <Layout> 
  10.       <Head> 
  11.         <title>{postData.title}</title> 
  12.       </Head> 
  13.       <article> 
  14.         <h1 className={utilStyles.headingXl}>{postData.title}</h1> 
  15.         <div className={utilStyles.lightText}> 
  16.           <Date dateString={postData.date} /> 
  17.         </div> 
  18.         <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} /> 
  19.       </article> 
  20.     </Layout> 
  21.   ) 

可以理解为先创建一个工厂 page(例如pages/[路由参数1]/[路由参数2].js),接着getStaticPaths填充路由参数,getStaticProps({ params })根据参数请求不同数据,最后数据进入页面组件开始预渲染:

四.SSG 支持

最简单,同时性能也最优的预渲染方式就是静态生成(SSG),把组件渲染工作完全前移到编译时:

  1. (编译时)获取数据
  2. (编译时)渲染组件,生成 HTML

将生成的 HTML 静态资源托管到 Web 服务器或 CDN 即可,兼具 React 工程优势与 Web 极致性能

那么首先要解决如何获取数据的问题,Next.js 的做法是将页面依赖的数据集中管理起来:

  1. // pages/index.js 
  2. export default function Home(props) { ... } 
  3.  
  4. // 获取静态数据 
  5. export async function getStaticProps() { 
  6.   // Get external data from the file system, API, DB, etc. 
  7.   const data = ... 
  8.  
  9.   // The value of the `props` key will be 
  10.   //  passed to the `Home` component 
  11.   return { 
  12.     props: ... 
  13.   } 

其中,getStaticProps只在服务端执行(根本不会进入客户端 bundle),返回的静态数据会传递给页面组件(上例中的Home)。也就是说,要求通过getStaticProps提前备好页面所依赖的全部数据,数据 ready 之后组件才开始渲染,并生成 HTML

P.S.注意,只有页面能通过getStaticProps声明其数据依赖,普通组件不允许,所以要求将整页依赖的所有数据都组织到一处

至于渲染生成 HTML 的部分,借助React 提供的 SSR API即可完成

至此,只要是依赖数据有办法提前获取到的页面,理论上都可以编译生成静态 HTML,但 2 个问题也随之而来:

  • 数据可能会发生变化,已经生成的静态页面需要更新
  • 数据量可能会多到“永远”编译不完

以电商页面为例,要把海量商品数据全都编译成静态页面,几乎是不可能的(或许要编译一个世纪那么长),即便都生成了,商品信息也会时不时地更新,静态页面需要重新生成:

If your app has a very large number of static pages that depend on data (think: a very large e-commerce site). You want to pre-render all product pages, but then your builds would take forever.

因此,增量静态再生成(Incremental Static Regeneration)应运而生

ISR 支持

对于编译时无法穷举的海量页面以及需要更新的场景,Next.js 允许运行时再生成(相当于运行时静态化):

Incremental Static Regeneration allows you to update existing pages by re-rendering them in the background as traffic comes in.

例如:

  1. export async function getStaticProps() { 
  2.   const res = await fetch('https://.../posts'
  3.   const posts = await res.json() 
  4.  
  5.   return { 
  6.     props: { 
  7.       posts, 
  8.     }, 
  9.     // 设置有效期,开启ISR 
  10.     revalidate: 1, // In seconds 
  11.   } 

revalidate: 1表示运行时(用户请求打过来时)尝试重新生成静态 HTML,1秒最多重新生成一次

运行时静态生成需要一些时间(用户请求等着要 HTML),在此过程中有 3 种选择:

  • fallback: false:不降级,命中尚未生成静态页面的路由直接 404
  • fallback: true:降级,命中尚未生成静态页面的路由先返回降级页面(此时props为空,一般显示个 loading),静态生成 HTML 的同时会生成一份 JSON 供降级页面 CSR 使用,完成之后浏览器拿到数据(在客户端填上props),渲染出完整页面
  • fallback: 'blocking':不降级,并且要求用户请求一直等到新页面静态生成结束(实际上就是 SSR,渲染过程是阻塞的,只是完成之后会保留结果 HTML)

即结合路由(getStaticPaths)对尚未生成的页面进行降级,例如:

  1. // pages/index.js 
  2. import { useRouter } from 'next/router' 
  3.  
  4. function Post({ post }) { 
  5.   const router = useRouter() 
  6.  
  7.   // 渲染降级页面 
  8.   if (router.isFallback) { 
  9.     return <div>Loading...</div> 
  10.   } 
  11.  
  12.   // Render post... 
  13.  
  14. export async function getStaticPaths() { 
  15.   return { 
  16.     paths: [{ params: { id: '1' } }, { params: { id: '2' } }], 
  17.     // (页面级)降级策略,true表示遇到尚未生成的先给个降级页,生成完毕后客户端自动更新过来 
  18.     fallback: true
  19.   } 

P.S.具体见Incremental Static Regeneration、以及The fallback key

然而,并非所有场景都能愉快地在编译时静态生成。典型的,如果组件依赖的数据是动态的,显然无法在编译时预先取得数据,静态生成就无从谈起了

五.SSR 支持

对于编译时无法生成静态页面的场景,就不得不考虑 SSR 了:

区别于 SSG getStaticProps,Next.js 提供了 SSR 专用的getServerSideProps(context):

  1. // pages/index.js 
  2. export async function getServerSideProps(context) { 
  3.   const res = await fetch(`https://...`) 
  4.   const data = await res.json() 
  5.  
  6.   if (!data) { 
  7.     return { 
  8.       notFound: true
  9.     } 
  10.   } 
  11.  
  12.   return { 
  13.     props: {}, // will be passed to the page component as props 
  14.   } 

同样用来获取数据,与getStaticProps最大的区别在于每个请求过来时都执行,所以能够拿到请求上下文参数(context)

六.总结

围绕预渲染如何获取数据的问题,Next.js 探索出了别致的路由支持和精巧的 SSG、SSR 支持。不仅如此,Next.js 还提供了鱼和熊掌可以兼得的混用支持,不同渲染模式结合起来到底有多厉害,且看下篇分解

参考资料

  • Pages
  • Data Fetching
  • Create a Next.js App:教程有点意思
  • vercel/next-learn-starter:示例 Demo

原文链接:https://mp.weixin.qq.com/s/bS9GHni4ecnz9UFi9RD24Q

 

责任编辑:武晓燕 来源: 前端向后
相关推荐

2021-12-27 08:31:42

Next.js SSRSSG

2024-03-04 07:33:39

RemixReact框架

2023-01-03 08:00:00

2009-10-20 09:25:17

Java EE 6最终

2023-11-02 08:01:06

Next.jsReactWeb

2024-01-25 09:04:25

2024-02-05 11:55:41

Next.js开发URL

2024-03-05 19:17:37

2020-12-14 11:40:27

Next.js SSRReact

2024-03-29 08:32:01

Node.jsNext.js组件

2023-09-20 10:14:03

Next.js前端

2021-11-26 10:29:24

jsRemix开源

2009-03-02 09:22:39

OSGiJ2EEEclipse

2021-01-04 09:06:18

Next.js设计技巧

2023-11-23 10:45:13

Next.js 14Supabase

2023-11-16 07:43:26

Next.jsReact

2022-02-22 20:48:48

RemixNext.js框架

2023-10-27 15:13:12

Next.jsRust

2024-04-03 13:27:28

Next.js扩展项目

2023-11-07 11:47:59

点赞
收藏

51CTO技术栈公众号