React Static Site Generators - From Phenomic, Gatsby to React-Static with PostGraphile - Thoughts and Issues

Introduction

Some details about living on the bleeding edge of React static site generation having used Phenomic, Gatsby and React-Static.

Backstory

I have created websites using many different technologies over the years, from text editors, to custom template generators, different Java and .NET User Interface libraries, Drupal, WordPress, SharePoint, Umbraco, Metalsmith, Hexo, Hugo, Cycle.js and many others.

Why "Static" React Sites

For me the appeal was a cross between the speed of a traditional static HTML site on loading (with the React site being pre-rendered once and uploaded as HTML and JavaScript and JSON files), but with the flexibility of React to add components without a trip to a backend server to generate the site at load time.

This means I don't necessarily need to run a server for generating the pages and can use a web server to serve up static files. There is the option of hooking into services, either client or server side, if parts of the site require it.

Initially Choosing a "Static" Site Generator

When I first looked at creating a "static" website with React, there were two main contenders. Gatsby (before the GraphQL API) and Phenomic (before 1.0 major API change). I found Phenomic source code and API easier to understand. Phenomic was getting a lot of updates at the time, so I used it to develop a site.

Using a Data Source (Service backed by a Database) with a "Static" Site

Phenomic worked quite well. Eventually I wanted to generate some pages using data from a database. Out of the box, Phenomic did not have any easy way to hook into a database. I ended up writing some code to help me do this.

No React Async Rendering

As I recall, React before version 16 did not support asynchronous rendering, meaning I needed to generate the data before the render started. I wrote some code to push the data I required into a JSON file. I could pull this in during the React rendering to make the static page. During development it would use the database and query directly. For the static build, it would use the JSON files.

This worked, but Phenomic was doing a large rewrite and there were some issues that would not get fixed in the version I was using. Understandably new work was only going to be focused on the new version and progress had seemingly slowed on the project.

Checking out Gatsby

Around this time I decided to look at Gatsby again. Next.js sounded very interesting, but didn't seem to have as much out of the box for the site I was building. React-Static had not been created yet, but is the latest contender I have seen that has got some attention in this area.

GraphQL in Gatsby

GraphQL integration was one of the new features Gatsby introduced after I had originally investigated it. This sounded like it might be an interesting way to solve my problem of pulling data from the database. I also thought Gatsby was using Webpack higher than 1, but this turned out not to be a false assumption on my part.

Gatsby GraphQL Confusion

So it turns out, I and others were a bit confused about what GraphQL in Gatsby means. I originally thought it meant you could pull data directly from a GraphQL source, but this is not true (although I have since managed to do this with React-Static).

Gatsby works by massaging data into its own node format using "source" plugins. The node format is documented here.

You then perform GraphQL queries on the schema used by GraphQL. This meant I had to write some code in a source plugin to make my data conform to Gatsby.

There are a number of plugins for Gatsby that do this for different data sources. I was pulling data from a REST service backed with a database and there were no plugins that worked with my service. I ended up putting together a Gatsby plugin to massage my data into the Gatsby format. Now I could perform GraphQL queries on the Gatsby schema, but it is a two step manipulation of the data:

  1. Gatsby plugin to transform the REST data to the Gatsby format.
  2. GraphQL queries from Gatsby schema.

Gatsby GraphQL Thoughts

I managed to replace a WordPress with a Gatsby version, but some of my data was quite complex and it seemed like the data manipulation and querying were sometimes a hinderance and limiting.

Moving Away from Gatsby

A few things that made me think about changing static site generators again:

Webpack 1

Gatsby is still using the very old Webpack 1 that is not supported. The update for this is scheduled for Gatsby 2, but has been in the works for quite a while. This seemed to cause some issues with caching (it had to be turned off or it wouldn't reload pages when a new build was uploaded). The breaking point was that I was trying to use Firebase FireStore, but the production build of Gatsby would break seemingly due to Webpack 1 (other people got it working with the unreleased Gatsby 2 version). It also limited other benefits that came with newer versions of Webpack, like tree shaking, performance updates etc.

Gatsby Complexity

Gatsby seems quite complex and hard to understand. I thought React-Static looked simpler and easier. This is coming from the perspective of a programmer. A non-programmer might find the Gatsby system easier to setup if the Gatsby plugins do everything they need without having to write any code.

Gatsby Plugin System Changing

In the case of my data source plugin I had managed to connect my data using information in my data to generate the Gatsby Id without the need for any key maps to track which Gatsby Id related to my data id. The Gatsby Id can sometimes be used to link the data together and perform queries on related data, e.g., get the Products in a Shop from the Shop Query. This API seemed to be changing to use GUIDs as the Id. If this is mandatory, I would need to write more code to perform the mapping of this GUID to my data id so I could link the data together. Given this and other possible breaking changes, I thought now might be a good time to change if I was going to.

Manual Linking of Data

Sometimes I couldn't figure out how to link the data in the Gatsby format, or be able to query it the way I wanted. In these cases I might be doing multiple queries and then putting the data together manually by writing some code. Maybe some of this could have been solved with GraphQL schema stitching, but I am not sure.

Empty Data Fields in GraphQL Query

Sometimes when you create a GraphQL query, I had issues when there is no data. This meant I had some set defaults like empty string to return something and make the query runtime happy or it would explode when returning a query result.

Gatsby Node Format

A Gatsby node uses a special id in the root of the node. My data also had a field called id. So to store my data at the root like other data plugins seemed to do meant renaming my id to something else. I am not sure why the Gatsby id is not in the internal field. This meant I ended up creating a data field on all my types. This worked okay, but then my GraphQL queries became a bit more verbose as each nested connection link had to drill through data, eg:

edges { node { data { slug product { data { name

It looks like you might be able to [specify something other than id now in this commit], but I haven't checked if that would help.(https://github.com/gatsbyjs/gatsby/pull/4466).

React 16 and Other Dependencies

Support for the newer version of React was via a plugin, which seemed a bit weird and I didn't get it to work properly.

Other dependencies like Webpack also seem to lag behind.

Hard Coded File Locations

I'm not sure the best way to setup my system, but currently I have a project for each site. Each site imports from a TypeScript project for common code and also website specific code, kind of like a monorepo. I try put as much as I can in the shared TypeScript project so it is easier to refactor and I can use types. I try make the site project mainly configuration. Gatsby needed certain files in certain places, which made this harder to achieve.

Other Thoughts on Using Gatsby

JavaScript Files Outside src are Not Transpiled

In the project I was using, babel is only configured to transpile the src directory. This means the code you write in files like gatsby-config.js and gatsby-node.jsonly support native features available in the version ofnodeyou are using. Newer versions ofnode` have quite good support, although support for the new module system is one thing that you might find confusing and lacking.

So Which React "Static" Site Generator Next?

Go back to Phenomic? Phenomic had moved forward and had some cool ideas like supporting Reason (but I had enough cuts from bleeding edge for the moment, even though Reason and functional programming with types that compile to web sounds amazing). My main issue was wanting a good way to import my data. I wasn't quite sure how this worked in Phenomic after looking at the documentation. It seemed to have some query API that could handle paging, but I wasn't sure how the data was plugged in.

Next.js is really popular and I had been following it since being announced. Their static export functionality was added later though and more of an afterthought.

I had seen React-Static on announcement and had followed that too. It sound quite stable and looked really easy. A brief check and it seemed like most things could be achieved fairly easily based on what I was using from Gatsby, which surprised me given how much newer the project was. The big thing was it might work with some other crazy idea I had for the data import problem.

PostGraphile (formerly know as PostGraphQL)

So what if I could remove the need to transform my data like in Gatsby and using a GraphQL query directly to fetch my data?

PostGraphile was another project I had been keeping tabs on. PostGraphile can generate a GraphQL server using some smarts and taking a look at a PostgreSQL database and its schema. I happened to be using PostgreSQL behind my REST service.

PostGrapile: Wow, So Much For So Little

So I install PostGraphile and point it at my database. It loads up and I can query my top level tables. Wow, so easy! But there are no relations... yet. In Gatsby I had to manually set these up in my Gatsby data source plugin, but PostGraphile has a clever and simple way to achieve this.

PostGraphile Auto Relations

After a few minutes, by adding some foreign key constraints to the PostgreSQL database and some slight tweaks to my Gatsby GraphQL query I was pulling the same data with much less effort.

PostGraphile Computed Columns

Array Id Relations

But some of my relations were using array ids, due to table joins lacking support in admin-on-rest and you can't add foreign key constraints to array ids. But with the power of SQL and functions in PostgreSQL, I suddenly had a new column that made a relation for me. Joining the data with SQL I found much easier than the Gatsby GraphQL schema and JavaScript.

View Relations

Similarly, SQL Views don't have constraints either and can't automatically generate relations. I had some views, so I created more computed columns that created a relation for these. This allows queries on the view relations with GraphQL with with a few lines of SQL in a PostgreSQL function.

React-Static and PostGraphile

This PostGraphile and React-Static combination seemed to good to be true as a replacement for Gatsby, but so far it seems to be working and I have much simpler code.

I have traded some of the GraphQL complexity into PostGraphile, but the surrounding code is much smaller and simpler to write.

PostGraphile is also usable as a replacement or in addition to my REST service. Most of the code I wrote for Gatsby can only be used in Gatsby.

Prisma GraphQL

Prisma also sounds like an interesting alternative to Postgraphile. It provides a way of generating a GraphQL server from different databases, PostgreSQL being one of the options. It sounds like a bit more work to get up and running and is quite new and evolving.

Prisma is open source, written in Scala and TypeScript and uses Docker containers.

It came out of the people at GraphCool and you can find an intro blog post here.

React Static

Things I Like

  • Data Source: Grab data from any source and pass it to a page in a simple way. You can use any technology you want, files, markdown, JSON, REST, GraphQL etc.
  • Simplicity: React-Static seems easy to use and perhaps easier to maintain due to the simpler API if I needed to make some changes.
  • Sitemap handles noindex: The Sitemap will not include items with noindex defined in the route. Google Webmaster tools seem to complain about this and Gatsby I believe needs to change the default query for the plugin to achieve this.

Not Out of the Box, But examples

  • There are a number of project examples that will give you ideas on how to get started with certain things you might not get out of the box.
  • Markdown: not supported out of the box, but there are a couple of repositories not linked from the readme that seem useful:
    • https://github.com/pkrawc/react-static-markdown
    • https://github.com/EmilTholin/react-static-markdown-example
  • Google Analytics: I used a variation of this to get going with Analytics: https://github.com/nozzle/react-static/issues/412
  • A way to reload the client on data change notification.

Things I Would Like

  • I have run into some issues around the production build not loading certain pages (but not all) that use withRouteData similar to what is described here. On a refresh the page loads. I've been trying to debug it, but it is hard to pin down.
  • Route Components imported from another Project: I tried to make the Routes load a component from my TypeScript project, but I had some issue. After spending some time trying to figure it out, I gave up for now and replicate the structure in both projects. The React-Static site project structure only imports the component from the TypeScript project. There is a configuration string I can change to start importing again from the TypeScript project if I can figure the issue out.
  • To be able to cache routeInfo.json and react-static-routes.js files.