How Gatsby circumvented Next.js

The author of the article, the translation of which we publish today, works as a programmer at Antler. This company is a global startup generator. There are demo days at Antler several times a year, bringing together many startup creators and investors from around the world. The situation around COVID-19 forced Antler to translate its events into an online format.



The company wanted to make sure that visitors to their virtual events, without being distracted by anything, and not getting stuck anywhere, would see the most important thing. Namely, the ideas of startups presented to the public, expressed as the contents of web pages. Virtual demonstration days may be of interest to a fairly wide audience. Some members of this audience may be participating for the first time in something like this. Therefore, the company had to do everything in the best possible way and provide high speed loading of pages representing startups. They decided that this is just the case when a high-performance progressive web application (PWA, Progressive Web App) can come in handy. The main issue was finding the right technology for developing PWA.


Server rendering or static site generator?


To start, I’ll introduce you a little to the course. All our projects are based on React and the Material-UI library. As a result, we initially decided not to move away from this technology stack, which would allow us to ensure high development speed and make the new project compatible with what we already have. The key difference between this new project and our other React-applications was that the database for them was created using the create-react-app, and that they were fully rendered on the client (CSR, Client-Side Rendering). This, in particular, led to the fact that when the application was initially loaded, users were forced to observe a blank white screen while the projects JavaScript code was loading, processing and executing.

We needed an uncompromising level of performance. Therefore, we began to think about using either server-side rendering (SSR, Server-Side Rendering) or a static site generator (SSG, Static Site Generator) so that the initial loading of applications would be as quick as possible.

Our data is stored in Cloud Firestore, and we access them using Algolia. This allows us to control, at the database field level, public access to data with restricted API keys. This also improves query performance. From experience, we know that Algolia queries are faster than normal, and that the compressed Firestore JavaScript SDK is 86 KB in size . In the case of Algolia, this is 7.5 Kb .

In addition, we wanted to make the data that we give to customers as fresh as possible. This would help us to very quickly correct erroneous data that could be accidentally published. While the standard practice of SSG provides for the implementation of relevant requests for data during the assembly of the project, we expected that in our database the data will be written quite often. In particular, we are talking about data recording initiated by administrators using the firetable interface, and on the initiative of the founders of the projects using the web portal. This leads to competitive assembly of the project. In addition, due to the structural features of our database, minor changes may lead to new project assembly operations. This makes our CI / CD pipeline extremely inefficient. Therefore, we needed the requests to receive data from the repository to be executed every time a user requests a page to load. Unfortunately, this meant that our solution would not be an example of a “clean” SSG project.

Initially, our application was created based on Gatsby, since we already used landing pages built on Gatsby, and one of them already used the Material-UI library. The first version of the project formed a page that, while the data was being loaded, displayed a "skeleton". At the same time, the first contentful paint (FCP) was in the region of 1 second.


Downloading the "skeleton" of the page with subsequent data loading

The solution turned out to be interesting, but it had its drawbacks, because the data for the output of the page was downloaded on the client's initiative:

  • To see the contents of the page, users would have to wait for the download of this page itself and the data displayed in it, obtained through 4 requests to Algolia.
  • JS- . , React «» . DOM.
  • . , , .

As a result, over the long weekend, I decided to experiment with the SSR version of the project created using Next.js. Luckily for me, the documentation for Material-UI had an example project for Next.js. Therefore, I did not need to learn all this framework from scratch. I just had to look through some parts of the tutorial and documentation. I converted the application to a project that was rendered on the server. When a user requested a page load, the server executed requests for data necessary to fill the page. This step allowed us to solve all three of the above problems. Here are the test results for two application options.


Results of researching applications using Google PageSpeed ​​Insights . On the left is Gatsby (SSG), on the right is Next.js (SSR) ( original image )

The FCP for the Next.js version of the project was about 3 times higher than for its version based on Gatsby. The Gatsby version of the project had a Speed ​​Index of 3.3 seconds, while the Next.js version had 6.2 seconds. The time to the first byte (TTFB, Time To First Byte) was 2.56 seconds when using Next.js, and 10-20 ms when using Gatsby.

It should be noted that the Next.js version of the site was deployed to another service (here we used the ZEIT Now and Firebase Hosting services - this could also affect the increase in TTFB). But, despite this, it was clear that the transfer of data upload operations to the server made the site appear slower, despite the fact that all the page materials were loaded in about the same time. The fact is that in the Next.js-version of the project the user for some time sees only a blank white page.


Screen capture showing loading two versions of an application. Download did not complete at the same time. Records are synchronized the moment you press the Enter key.

All this gives us an important lesson from the field of web development: you need to give users visual feedback. One study found that applications that use skeletal screens seem to load faster.

This result, moreover, does not correspond to the mood that you might have caught if you read articles about web development in the last few years. Namely, we are talking about the fact that there is nothing wrong with using the client’s resources, and that the SSR is not a comprehensive solution to performance problems.

Static Site Generation Performance: Comparing Gatsby and Next.js


While the two frameworks under consideration, Gatsby and Next.js, are known, respectively, for their ability to generate static sites and server rendering, support for SSG was improved in Next.js 9.3 , which makes it a competitor to Gatsby.

At the time of this writing, Next.js' ability to generate static sites was still very fresh. She was a little over a month. She is still reported on the first page of the project. Now there are not many comparisons of the SSG capabilities of Gatsby and Next.js (or maybe there are no such comparisons at all yet). As a result, I decided to conduct my own experiment.

I returned the Gatsby version of the project to the state when the data was downloaded on the client, and made it so that both versions of the application would have exactly the same set of features. Namely, I had to remove what Gatsby plugins are responsible for: SEO functions, generating favicons, PWA manifest. In order to compare exclusively JavaScript bundles created by frameworks, I did not include images and other content downloaded from external sources in the projects. Both versions of the application were deployed on the Firebase Hosting platform. For reference, two versions of the application were created based on Gatsby 2.20.9 and Next.js 9.3.4.

I ran Lighthouse on my computer 6 times for each version. The results showed a slight advantage to Gatsby.


Average values ​​obtained after 6 Lighthouse launches for each framework ( original image )

In terms of overall performance assessment, the Next.js version was only slightly behind the Gatsby version. The same goes for FCP and Speed ​​Index. The Next Potential First Input Delay for Next.js version of the application is slightly higher than for the Gatsby version.

In order to better understand what is happening, I turned to the Network tab of the Chrome developer tools. As it turned out, in the Next.js version of the project, the number of fragments into which the JavaScript code is split is 3 more than in the Gatsby version (excluding manifest files), but the compressed code is 20 KB smaller. Can the extra requests needed to download these files outweigh the benefits of a smaller bundle so much that it hurts performance?


In the Gatsby version of the project, 7 requests are executed to download 379 KB of data. In the Next.js version of the project - 12 requests for downloading 359 KB of data (the original image )

If you analyze the JavaScript performance, then the developer tools say that the Next.js version of the project needs an additional 300 ms for the first rendering, and that this version spends more time on the Evaluate Script task. In the developer’s tools, this task was even marked as a “long task”.


Analysis of the performance of different project options using the Performance tab of the Chrome developer tools ( original image )

I compared the project code to find out if there are any differences in their implementation that could affect performance. With the exception of removing unnecessary code and corrections associated with missing TypeScript types, the only difference was the implementation of smooth scrolling of the page when moving to its individual parts. This feature was previously introduced by a filegatsby-browser.jsand was moved to a dynamically imported component . As a result, this code would only run in a browser. (We used the smooth-scroll npm package, and when importing it, he needs an objectwindow.) This problem may be the culprit, but I just don’t know how it is handled in Next.js.

Gatsby is more convenient from the point of view of the developer


In the end, I decided to opt for the Gatsby version of the project. Moreover, here I did not take into account the very small performance advantage that Gatsby showed in comparison with the Next.js SSG mechanism (I won’t seriously cling to the advantage of 0.6 seconds?). The fact is that many features of PWA are already implemented in the Gatsby version of the project, and I did not see the point of implementing them again in the Next.js version of the application.

When I was just creating the first Gatsby version of the project, I was able to quickly add some useful PWA features to the project. For example, to add to each page my own meta tags needed for SEO, I just had to read the manual . To equip the project with a PWA manifest, I only needed to use the appropriate plugin. In order to equip the project with favicons that would support all available platforms (and in this case there is still a terrible mess ), I did not even have to do anything, since favicon support is part of the plugin responsible for the manifest. It is very comfortable!

Implementing the same features in the Next.js version of the application would require more work. I would have to look for training manuals, all sorts of “best practices”. And the fact that I would succeed, anyway, would not give me any advantages. After all, nevertheless, the Next.js version of the project does not differ in higher performance than its Gatsby version. This, in addition, was the reason why I decided to simply disable the corresponding features of the Gatsby version of the project, comparing it with the Next.js version. The Next.js documentation is more concise than the Gatsby documentation (maybe the fact is that Next.js is smaller than Gatsby) I really like the gamified Next.js. tutorial But the more extensive Gatsby documentation is more valuable with the actual development of PWA, even though at first glance it looks huge.


Gatsby Documentation

True, I can't keep silent about Next.js strengths:

  • Thanks to the tutorial and the concise documentation of Next.js, it feels like this framework can be learned faster than Gatsby.
  • The data loading system used in Next.js is based on asynchronous functions and the Fetch API. As a result, when developing Next.js, the developer does not have the feeling that he needs to learn GraphQL in order to take full advantage of the framework's capabilities.
  • Next.js TypeScript, Gatsby , ( ). Next.js , , , .

Thanks to the fact that Next.js has improved SSG support, this framework has become a powerful tool that allows, at the level of each individual page, to choose the method of working with it. It can be SSR, SSG or CSR.

In fact, if I could generate this application in a completely static form, then Next.js would suit me better, since I could use the standard Algolia JS-API and could keep the code for loading data in the same file as and component code. Since Algolia does not have a built-in GraphQL API, and there is no Gatsby plugin for Algolia, implementing such a mechanism in Gatsby would require adding this code to a new file . And this goes against the intuitive declarative way of describing pages.

About Additional Ways to Improve Project Performance


After we have solved the problem of choosing a framework, it can be noted that there are additional ways to improve project performance that are not related to the framework. These improvements may well bring the Lighthouse project rating to 100.

  • In the March Algolia mailing list, it was recommended to add a hint preconnectto further increase the speed of query execution. (True, unfortunately, the wrong code fragment is given in the newsletter. Here is the correct code.)
  • . JS- CSS-, webpack- Gatsby. Gatsby . , , Netlify Amazon S3. , Firebase Hosting, , .
  • We use JPEG and PNG images uploaded by startup creators in the application. We do not compress and optimize them. Improving this aspect of our application is quite a challenge and is beyond the scope of this project. In addition, it would be just great if all of these images were converted to WebP format. As a result, we would have to store images using only one highly efficient graphic format. Unfortunately, as with many other PWA features, the Safari WebKit development team is addictive with WebP support. Now it is the only major browser that does not support this format.

Summary


If we briefly summarize what we were talking about here, then we can say the following:

  • The output of the "skeletal" version of the page during the data loading by the client creates the user a feeling of faster website operation than when the user looks at a blank page while the server is loading data.
  • The gatsby version of the site was only slightly faster than the Next.js version. However, the Gatsby plug-in system and high-quality project documentation increase the usability of this framework for the developer.

Dear readers! Do you use static site generators or server-side rendering systems to speed up your projects?


All Articles