当前位置: 首页 > Web前端 > HTML

Next.js实践:从SSR到CSR的优雅降级

时间:2023-03-29 12:32:38 HTML

Next.js是一个非常流行的React框架,它提供了开箱即用的服务器端渲染(Server-SideRendering,SSR)能力,我们可以轻松地使用Next.js轻松实施SSR并将其部署在Vercel上。但是Vercel在国内没有服务节点,SSR时通过Vercel服务器请求国内服务接口有一定比例失败,影响页面渲染。因此,我们需要加入Client-SideRendering(CSR)的逻辑作为掩护,以提高渲染的成功率。在Next.js中,我们可以通过在页面组件中添加getServerSideProps方法来触发SSR。比如我们调用接口根据页面URL上的路径参数id获取详细信息,并将数据提供给页面组件方法进行渲染。代码如下:interfaceProps{detail:Detail;}asyncfunctiongetServerSideProps(context:GetServerSidePropsContext):Promise<{props:Props}>{const{id}=context.params;constdetail=awaitgetDetail(id);return{props:{detail,},};}Next.js会将上下文传递给getServerSideProps方法,其中包含路由和请求参数信息;我们返回一个包含props的对象,在服务端渲染中通过props将获取到的数据提供给页面组件。constPage:React.FC=props=>{return

detail的渲染逻辑{props.detail}
;};可以看出,页面组件强烈依赖服务器传给props的数据,如果请求失败,则页面无法正常渲染。实现CSR自下而上的逻辑首先,我们需要修改getServerSideProps,增加请求出错时的容错能力:asyncfunctiongetServerSideProps(context:GetServerSidePropsContext):Promise<{props:Props|{}}>{const{id}=context.params;尝试{constdetail=awaitgetDetail(id);返回{道具:{细节,},};}catch{return{props:{},};}}当请求失败时,我们使用catch来捕获错误,并将props设置为{}以通知组件获取数据失败。我们在组件中使用useEffect再次发起请求:constPage:React.FC=props=>{constisEmptyProps=Object.keys(props).length===0;const[细节,setDetail]=useState(props.detail);const路由器=useRouter();useEffect(()=>{if(isEmptyProps){const{id}=router.query;getDetail(id).then(props=>setDetail(props.detail));}},[]);返回
detail的渲染逻辑{detail}
;};通过判断props.detail是否为空对象,我们决定是否在浏览器端再次发起请求,请求参数id可以通过Next.js提供的next/router中的useRouter获取。数据获取成功后保存在组件的State中。至此我们完成了页面组件从SSR到CSR的降级。但是我们需要支持降级的页面很多,一个一个改造成本太高,所以我们需要对这些逻辑进行抽象。抽象降级的逻辑从上面的实现过程来看,降级的逻辑主要分为两步:getServerSideProps添加错误捕获,当错误发生时,props返回{}页面组件判断props是否为空对象,以及如果是,再次发起请求获取数据对于这两个步骤,我们可以分别抽象:抽象SSR错误捕获我们可以通过定义一个工厂函数,在原来的getServerSideProps中添加错误捕获:functioncreateGetServerSidePropsFunction(getServerSideProps:F):F{returnasync(context:GetServerSidePropsContext)=>{try{returnawaitgetServerSideProps(context);}catch{return{props:{},};}};}导出这个工厂函数生成的函数:exportconstgetServerSideProps=createGetServerSidePropsFunction(getServerSidePropsOrigin);抽象CSR数据请求我们可以实现一个高阶组件(Higher-OrderComponents,HOC)来抽象这部分逻辑:functionwithCSR,Pextendsobject=React.ComponentProps>(组件:C,getServerSideProps:GetServerSideProps){constHoC=(props:P)=>{const[newProps,setNewProps]=useState

(props);const路由器=useRouter();constisPropsEmpty=Object.keys(props).length===0;useEffect(()=>{if(isPropsEmpty){constcontext:GetServerSidePropsContext={locale:router.locale,locales:router.locales,defaultLocale:router.defaultLocale,params:router.query,query:router.query,resolvedUrl:router.asPath,req:{}未知,res:{}未知,};getServerSideProps(context).then(setNewProps);}},[]);if(newProps){returnReact.createElement(component,newProps);}返回空值;};HoC.displayName=`Hoc(${component.name})`;returnHoC;}逻辑和前面的实现基本一样,在高层组件中,如果SSR失败,我们在浏览器端再次调用getServerSideProps发起请求。这时候我们就需要构造一个和Next.js一致的context。这里我们选择从next/router获取相关信息并构建。最后,我们将页面组件传给高阶组件,返回一个新的页面组件并导出。导出默认withCSR(Page,getServerSidePropsOrigin);页面组件访问的两步抽象完成后,我们页面组件降级的实现就变得非常简单了。在不需要修改页面组件和getServerSideProps的基础上,我们只需要添加如下一行:import{withCSR,getServerSideProps}from'../ssr-fallback';exportconstgetServerSideProps=createGetServerSidePropsFunction(getServerSidePropsOrigin);导出默认withCSR(Page,getServerSidePropsOrigin);到目前为止,我们已经实现了从SSR到CSR的优雅降级。进一步抽象成一个npm包我们将上述逻辑抽象成一个npm包next-ssr-fallback,并进一步抽象出SSRFallback类,简化getServerSideProps的传递。用法如下:importFallbackSSRfrom'next-ssr-fallback';constfallbackSSR=newFallbackSSR({getServerSideProps:getServerSidePropsOrigin,});exportconstgetServerSideProps=fallbackSSR.createGetServerSidePropsFunction();exportdefaultfallbackextSSR.withCSR-Page);ssr-fallback的GitHub仓库:https://github.com/crazyurus/next-ssr-fallback还有一个连接next-ssr-fallback的Next.js项目recruit-pc供参考,项目部署在Vercelhttps://recruit-pc.vercel.app总结当我们在Next.js中遇到SSR渲染失败的问题时,我们选择降级为CSR来提高渲染成功率,并将这部分实现逻辑抽象为next-ssr-fallback可以在其他项目中复用,实现更优雅的降级。如果大家有其他SSR降级相关的做法,欢迎分享