React Frameworks for Static Generation
7 mins

When I was planning an update for this website, I was faced with choosing the most fitting tech stack. Thanks to the rising the popularity of Node.js, the past 5 years has witnessed a rapid evolution of build tools for web development. Almost every JavaScript framework has its own packages of build tools now. Static pages and assets can be deployed to any CDN or hosting services. For this website, blog posts and portfolio contents are the primary source of data. If the data size is small and can be included during the build process, it removes the need and complexity of servers and databases all together. Each update of the data or the source codes is followed by a rebuild before deploying to the hosting service, and the release process can be automated. In case it's necessary to run a few server-side tasks, it can be handled painlessly by independent serverless functions on major cloud platforms, thanks to the rise of serverless computing in recent years.

Static site generators are very suitable for blog posts, developer documentation or marketing pages, and JAMstack becomes a recent buzzword. For this website I prefer Node.js for the build process, Markdown or MDX as content container, and React for the presentational layer.

Static Generation

Server side rendering (SSR) and client side rendering (CSR) are two opposite approaches for rendering on the web, and each has its pros and cons. Modern JavaScript frameworks take the CSR approach. It scales by bundles size and it's generally cheaper than SSR which scales by infrastructure. However, the initial bare-bones HTML in CSR is a nightmare for search engine optimization. Static generation is proposed as a remedy for this, and it is ultra fast to send static files built ahead of time. Due to its performance merit, static generation is adopted both in SSR and CSR. With static generation you can decide whether or not to bundle data into production builds. There is a chance that the data become stale at request time if they are pre-packed in the build process. Sometimes serving stale data is not a big problem if it doesn't affect the audience or crawlers. If it is really important to keep the data up to date, it's suggested to fetch data at request time, either from the server or the client side, and consider static generation for the presentational parts only. The bottleneck of static generation is the length of build time, which is determined by the data size, bundles size and update frequency. It's ideal for small blogs, but not so optimal for large sites with rapid data updates.

Frameworks for Static Generation

I have been happy with Vue until last year when I used it for server side rendering in a project. I came across a couple of issues that I couldn't find an answer with search engines. I ended up rewriting the development server logic and the webpack configs from the official demo vue-hackernews-2.0. The same questions, however, can be answered in React. An active developer community matters a lot when choosing a framework, because it is more likely to find a solution there when problem arises.

I am not using server side rendering for this site. Instead, static contents are generated in advance. Since the static rendering at build time is almost identical to the phase of generating static HTML in server side rendering, I want to try something new outside the Vue ecosystem. React seems to have a more mature ecosystem, and it makes sense to consider React in this scenario.

Gatsby.js or Next.js ?

In the React's ecosystem, both Gatsby and NextJS can generate static contents. However, there are differences between them. Gatsby claims that it is doing a better job in image optimization, accessibility and data sourcing.

Gatsby is a static site generator (SSG) in the first place, but it never stops you from having dynamic contents via client-side rehydration with React. With Gatsby you can always create hybrid pages that fetch static data at build time and load dynamic data in client-side runtime. However, there is a catch you need to be careful with. The routes created in the build process and the routing in browser can be different. Gatsby uses @reach-router, not the popular browser-oriented @react-router-dom for navigation. The navigation links created in Gatsby correspond to the static files in the /public directory by default. The client-only routes, which are controlled by JavaScript in the browsers, don't have a matching file in the /public directory. If you want to expose private routes for authenticated users with client-side JavaScript, check the documentation to adjust your source codes and set up hosting services for URL redirect for the client-only routes. Apart from the nitty-gritty of routing, making websites with Gatsby is not much different from writing single-page application (SPA) with React.

NextJS focuses on the server side rendering (SSR) from the beginning and that's what it is most famous for. It is catching up with static generation recently. The support for static html export at build time has been introduced since v3 (2017.05). In a server runtime environment, automatic static optimization is enabled by default in v9.3+ (released in 2020.03) and stable incremental static regeneration has been added into the picture in v9.5+ (released in 2020.07). NextJS uses file-system based routing for navigation between pages, and has a separate API next/link exclusive for the client-side navigation.

Data Fetching

Static files can be generated with or without data. However, the strategy and timing of data fetching makes a big difference. Gatsby primarily relies on GraphQL to source data in build time. NextJS allows data fetching both in build time and runtime, and permits periodic updates of data in the background.

Gatsby ships GraphQL out of the box for data sourcing. Although it is possible to use Gatsby without GraphQL, GraphQL is recommended as the data layer in official tutorials and documentation. Gatsby claims superiority in data sourcing. The integration of GraphQL in the build process, together with a rich plugins ecosystem, contributes the popularity of Gatsby. With source plugins and transformer plugins, you can fetch and process data from local file systems, APIs, CMS and databases. Data are modelled as nodes objects in Gatsby and exposed with explicit or inferred types to GraphQL in the bootstrap phase. In the build phase, Gatsby runs queries from components and pages, loads JSON data from GraphQL, and merges the data with components and pages to create static HTML files. The heavy lifting of fetching and structuring data through GraphQL has been done internally in Gatsby. It becomes an one-stop solution to those who want to migrate from traditional CMS-based platforms to a more cost-efficient static hosting services.

NextJS has more flexibility in general and is suitable for SSR or a hybrid of SSR with static generation. If you are building an e-commerce site with 100,000+ products, a full rebuild is slow and unrealistic when update gets frequent. This e-commerce example adopts static generation for pages or parts of the pages that look the same to all users and rarely change between requests. For existing pages that are pre-rendered with data and may change at some point, fetching data and partial rebuild can be scheduled periodically in the background. It is called incremental static (re)generation and can be achieved with the getStaticProps and revalidate APIs. For user-specific contents where SEO doesn't matter, they can be statically generated without data ahead of time and rely on client-side data fetching like most SPA. Static generation is designed to offload pressure on the backend.

Final Thoughts

Both Gatsby and NextJS are capable of building static websites. Powered by GraphQL, Gatsby has some edges in data sourcing and the plugins ecosystem. The limitation is that it doesn't scale well as a website grows to a point when update becomes frequent, and the build time increases with the size of bundles. To mitigate the problem, the incremental builds feature was introduced in Gatsby v2.20.4. It was initially only accessible in Gatsby Cloud (2020.04), but became available in the open-source version of Gatsby v3+ (2021.03).

Leveraging the power of incremental static generation, NextJS is a good fit for creating and updating websites large and small. The flexibility also means less overhead. Not everyone needs GraphQL as data layer for static pages. Static files can be generated without data after all. Decoupling static generation from data source is sustainable in the long run. Although NextJS doesn't have a rich plugins ecosystem comparable to Gatsby, you will probably like it for its pristine structure and easy-to-follow documentation.