切换到Next.js并加快歧管网站首页的加载7.5倍

今天,我们正在发布一个故事的译文,该故事讲述了从React BoilerplateNext.js (一个用于开发基于React的渐进式Web应用程序的框架)的过渡如何将歧管项目首页的加载速度提高了7.5倍。 没有对该项目进行任何其他更改,并且这种转换通常对于系统的其他部分来说是完全不可见的。 最终结果甚至比预期的要好。



结果概述


实际上,我们可以说,向Next.js的过渡给我们带来了诸如“无处不在的项目生产率提高”之类的东西。 这是使用各种硬件资源和网络连接时项目加载时间的样子。
连接方式
中央处理器
到秒
几秒钟后
改善百分比
快速(200 Mbps)
快的
1.5
0.2
750
中(3G)
快的
5.6
1.1
500
中(3G)
中号
7.5
1.3
570
慢(3G连接慢)
中号
22
4
550

当使用快速连接和带有快速处理器的设备时,站点加载时间从1.5 s下降。 最高可达0.2 s。,即该指标提高了7.5倍。 在中等质量的连接上以及在具有平均性能的设备上,站点加载时间从7.5 s下降。 高达1.3秒

用户单击URL后会发生什么?


为了了解渐进式Web应用程序(Progressive Web App,PWA)的功能,您必须首先了解用户单击URL(在我们网站的地址)的那一刻到看到某物的那一刻之间发生的情况。在浏览器窗口(在本例中为我们的React应用程序)中。


应用阶段

考虑使用该应用程序的5个阶段,上面给出了其示意图。

  1. 用户进入URL,系统使用DNS查找服务器地址并访问服务器。 所有这些操作都非常快地完成,通常花费不到100毫秒,但是此步骤需要一些时间,因此在这里提到它。
  2. 现在服务器返回页面的HTML代码,但是浏览器中的页面将保持空白,直到加载其显示所需的资源为止(除非资源是异步加载的)。 实际上,此阶段正在采取的行动比图表中所示的要多,但是对所有这些过程的联合审核也将适合我们。
  3. 加载HTML代码和最重要的资源后,浏览器开始显示其可以显示的内容,并继续在后台加载其他所有内容(例如图片)。 您是否曾经想过,为什么有时图像突然突然“弹出”在页面上的速度明显超过了必要,而有时却加载时间太长? 这就是为什么发生这种情况。 这种方法使您可以快速创建完成的页面。
  4. JavaScript代码只有在加载后才能解析和执行。 根据页面上使用的JS代码的大小(对于典型的React应用程序,如果代码打包在单个文件中,这可能会很大),可能要花费几秒钟甚至更长的时间(请注意,JS该代码不需要为了开始执行而等待所有其他资源的加载,尽管事实上它在图中看起来完全像这样)。
  5. 对于React应用程序,现在是代码修改DOM的时刻,这将导致浏览器重新绘制已经显示的页面。 然后另一个资源加载周期开始。 此步骤花费的时间将取决于页面的复杂程度。

越快越好。


由于渐进式Web应用程序需要React代码并生成静态HTML和CSS代码,因此这意味着用户已经在上述方案的步骤3而非步骤5看到了React应用程序。在我们的测试中,这需要0.2到4秒的时间,这取决于用户连接到互联网及其设备的速度。 这比之前的1.5-22秒要好得多。 渐进式Web应用程序是将React应用程序更快地交付给用户的可靠方法。

渐进式Web应用程序和诸如Next.js之类的相关框架仍然不流行的原因是,传统上,JS框架在生成静态HTML代码方面并不是特别成功。 如今,由于React,Vue和Angular等框架对服务器端渲染提供了出色的支持,因此一切都发生了很大变化。 但是,要使用这些工具,您仍然需要深入了解捆绑器的工作功能和用于构建项目的工具。 处理所有这些并非没有问题。

由于进入门槛较低,并且由于使用这样的框架使PWA成为一项简单而令人愉快的任务,因此最近出现的Next.js和Gatsby之类的PWA框架(均于2016年底至2017年初出现)已成为迈向PWA广泛采用的重要一步。

尽管不是每个应用程序都可以转移到Next.js,但是对于许多React应用程序而言,这种过渡意味着我们在这里所谈论的“无处不在的性能”,并以更加有效的网络资源使用为补充。

迁移到Next.js有多困难?


通常,可以注意到将我们的主页转换为Next.js并不是很困难。 但是,我们遇到了一些由应用程序的体系结构功能引起的困难。

▍拒绝反应路由器


我们不得不放弃React路由器,因为Next.js拥有自己的内置路由器,最好将其与在PWA架构之上执行的有关代码分离的优化相结合。 这使该路由器能够比您从任何客户端路由器获得的页面加载速度快得多。

Next.js路由器有点像高速React路由器,但它仍然不是React路由器。

在实践中,由于我们没有利用React路由器提供的特别高级的功能,因此向我们过渡到Next.js路由器的方法是简单地将标准的React路由器组件替换为相应的Next.js组件:

/*   ( React) */ <Link to="/my/page">  A link </Link> /*   ( Next.js) */ <Link href="/my/page" passHref>  <a>    A link  </a> </Link> 

总的来说,一切都还不错。 我们必须重命名该属性并添加标记以用于服务器渲染。 由于我们还使用了styled-components库,因此事实证明,在大多数情况下,我们需要添加passHref属性,以确保系统的行为方式是href始终指向生成的标记。


Network对歧管的要求

为了用肉眼看到运行中的Next.js路由器的优化,请通过查看歧管页面来打开浏览器开发人员工具的“网络”选项卡,然后单击某些链接。 上图显示了单击/services链接的结果。 如您所见,它导致执行加载services.js的请求,而不是执行常规请求。

我不仅在谈论客户端路由; React路由器也适合解决此问题。 我说的是一段真实的JavaScript代码,该代码已从其余代码中提取并按要求加载。 这是使用标准的Next.js完成的。 这比我们以前拥有的要好得多。 即,我们正在谈论的是1.7 MB大小的大型JS代码包,客户端在看到之前必须下载并处理。

尽管此处介绍的解决方案并不完美,但它比上一个解决方案更接近于用户仅下载其查看页面的代码这一想法。

Red使用Redux的功能


继续讨论与向Next.js过渡相关的困难这一主题,可以注意到,Next.js对应用程序进行的所有有趣的优化都对该应用程序产生一定的影响。 即,因为Next.js执行页面级代码分离,所以它防止开发人员访问React根组件或react-dom库的render()方法。 如果您已经参与配置Redux,那么您可以注意到,所有这些都告诉我们,对于Redux的正常运行,我们需要解决问题,即不清楚在哪里寻找Redux。

Next.js提供了一个特殊的高阶组件withReduxwithRedux每个页面上所有顶级组件的包装:

 export default withRedux(HomePage); 

尽管这createStore()不错,但是如果您需要createStore()方法(例如,在使用redux-reducer-injectors时) ,则希望您需要更多的时间来调试包装器(顺便说一下,请不要尝试使用类似redux-reducer-injectors东西。

此外,由于Redux现在是一个“黑匣子”,因此使用不可变库会带来问题。 尽管Immutable可与Redux一起使用的事实似乎很明显,但我遇到了一个问题。 因此,要么顶层状态不是不可变的( get is not a function错误),要么包装器组件试图使用点表示法而不是.get()方法来处理JS对象( Can't get catalog of undefined错误的Can't get catalog of undefined )。 要调试此问题,我必须参考源代码。 毕竟,Next.js出于某种原因强迫开发人员使用其自己的机制。

总的来说,可以注意到与Next.js相关的主要问题是,在这个框架中,很少有文件记载得很好。 文档中有许多示例,您可以在这些示例的基础上创建自己的东西,但是如果其中没有一个示例可以反映项目的功能,则只能祝您好运。

etch获取拒绝


我们使用了react-inlinesvg库 ,该为嵌入式SVG图像和查询缓存提供样式选项。 但是这里我们有一个问题:执行服务器渲染时,没有XHR请求之类的东西(至少不是Webpack生成的URL所期望的那样)。 尝试执行此类请求会干扰服务器渲染。

尽管还有其他用于处理支持SSR的嵌入式SVG数据的库,但我还是决定放弃此功能,因为SVG文件仍然很少使用。 如果在显示相应图像时不需要样式,则可以用普通图像, <img>标签替换它们,也可以将它们以React JSX的形式嵌入到代码中。 可能一切都变得更好了,因为当页面首次加载并且发送给客户端的JS捆绑包少了1个库时,JSX插图现在已经在浏览器中了。

如果您需要使用数据加载机制(我需要另一个库使用此功能),则可以使用whatwg-fetchnode-fetchnext.config.js进行配置:

 module.exports = { webpack: (config, options) =>   Object.assign(config, {     plugins: config.plugins.concat([       new webpack.ProvidePlugin(         config.isServer           ? {}           : { fetch: 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch' }       ),     ]),   resolve: Object.assign(config.resolve, {     alias: Object.assign(       config.resolve.alias,       config.isServer ? {} : { fetch: 'node-fetch' }     ),   }), }), }; 

▍客户端和服务器JS


我想在这里提到的Next.js的最后一个功能是,该框架被启动了两次-一次是针对服务器,另一次是针对客户端。 这会稍微模糊同一代码库中客户端JavaScript和Node.js代码之间的界线,从而在尝试利用客户端上的Node.js功能时导致fs is undefined类的异常错误。

结果,我们必须在next.js.config构造这样的构造:

 module.exports = { webpack: (config, options) =>   Object.assign(config, {     node: config.isServer ? undefined : { fs: 'empty' },   }), }; 

如果您需要在不同的环境中运行相同的代码,Webpack中的config.isServer标志将是您最好的朋友。

此外,除了React组件生命周期的标准方法外,Next.js还支持getInitialProps()方法,该方法仅在代码以服务器模式getInitialProps()时才被调用:

 class HomePage extends React.Component { static getInitialProps() {   //         } componentDidMount() {   //     ,    } … } 

是的,我们不要忘记,Node.js中没有组织好监听事件,确定浏览器窗口大小并提供许多有用功能的访问权限的好朋友window对象:

 if (typeof window !== 'undefined') { // ,     `window`      } 

应该注意的是,即使Next.js也无法使开发人员免于解决与在服务器和客户端上执行相同代码相关的问题的需要。 但是在解决此类问题时, config.isServergetInitialProps()非常有用。

结果:Next.js之后会发生什么?


短期而言,Next.js框架在性能,服务器渲染要求以及在禁用JavaScript的设备上查看网站的能力方面都可以完美匹配。 另外,现在,它允许您使用高级(丰富) 元标记

也许将来,如果我们的应用程序需要服务器渲染和更复杂的服务器逻辑,我们将考虑其他选择(例如,我们研究了在岐管网和dashboard.manifold.co上实现单点登录技术的可能性) ) 但是在此之前,我们将使用Next.js,因为该框架耗时少,为我们带来了巨大的收益。

亲爱的读者们! 您在项目中使用Next.js吗?

Source: https://habr.com/ru/post/zh-CN442654/


All Articles