Skip to content

Instantly share code, notes, and snippets.

@lostfictions
Last active March 22, 2019 21:45
Show Gist options
  • Save lostfictions/5744c39e65f166d4b9f143c5c3b6f319 to your computer and use it in GitHub Desktop.
Save lostfictions/5744c39e65f166d4b9f143c5c3b6f319 to your computer and use it in GitHub Desktop.
Some Notes on Static Site Generators

(work in progress! notes will be updated as generators are re-evaluated.)

A Tale of Three Generators

Static site generators are far from a solved problem. It's a deceptively simple proposition: grab some data -- say, blog posts written in Markdown files -- stick them in templates, and spit out a set of HTML files, ready to be served as-is on something like GitHub Pages, no complex server code needed. And indeed, there have been hundreds of takes on the idea, running the gamut of pretty much every programming and templating language imaginable.

I thought I'd make some notes on my experience using some generators. I'm at least partly doing this for myself, since I seem to have to re-evaluate my choices every time I go to make a new site (which is often). But I think it might be valuable to others to have some qualitative notes on the experience of using these things; sites like staticgen.com only allow you to evaluate on the basis of things like "how many GitHub stars does it have?" -- a metric we should know by now is flawed at best.

Here are the three generators I've considered and used:

Why these three? First, unlike something like Hugo, they're all Node-based. That's just an issue of familiarity for me, but since (in my experience) static generators almost always need tweaking or extension to accommodate the particular requirements of a given site (like, say, localization support) familiarity is pretty critical.

Second, and corollary to that, they all use React for templating. This is also a matter of familiarity and comfort for me, though arguably React is also one of the most capable and intuitive libraries out there to have solved the problem of web components. But since React is foremost a client-side library, it also empowers us to do some neat things, like animate page transitions or allow the editing of pages with a full, accurate live preview in netlify-cms. This is pretty huge. It also allows these generators to implement the PRPL pattern, and it really shows -- page loads and time-to-interactive for all of them are blazing fast. Perhaps most importantly, the React ecosystem makes it easy for us to opt in to other best practices for sustainable, pleasant web development, like using some form of scoped CSS.

Before diving in, it might be valuable to mention that the react-static developers wrote a comparison of the same three generators themselves when they launched. I broadly agree with their conclusions, though I think the landscape has changed enough in the past year and a half for some of this stuff to no longer be true -- in particular, next.js seems to have improved their static build capabilities and optimized build bundle sizes.


gatsby

  • gatsby is big! gatsby is full-featured! gatsby does a lot. it rose to enormous popularity very quickly, and for good reason -- it was one of the first React-based static generators and among the very first of any generators to implement PRPL.

  • needlessly complicated graphql-based model. i like gql, but using it to fetch and munge a few markdown files from the local filesystem is super unintuitive and feels like tremendous overkill. it seems as though gatsby uses graphql because... it was shiny and new at the time?

    i could maybe imagine it making sense in some scenario where you need to gather data from a large number of heterogenous, possibly remote sources or something, and build complex pages based on queries that are also heterogeneous, cross-cutting, and layered. but i'm probably never going to work at that scale, and it complicates the base use case enormously.

  • almost entirely plugin-driven. this can seem like a great advantage: the whole idea is that you have all these little bits of functionality that you can drop in and chain together. and sometimes it works great! but it also means if something doesn't work -- if there's a small bit of missing functionality in a given plugin (or none available that solve your specific issue), if there's a bug in a plugin, or (much more common) if there's a problem where plugins don't play well together -- your main recourse is... to drop down to writing a plugin to fix it. in my experience this leads to a lot of unnecessary scaffolding and boilerplate, and a suboptimal debugging experience (unless there's extensive infrastructure for testing and debugging plugins in isolation or together, which is sadly almost never the case).

    oftentimes i find all i really need when building a static site is a few lines of code that grabs some data and munges it in one or two particular ways. having to build a set of graphql apis and independent pipeline stages is often not just clumsy, but hard to reason about.

  • all the same, i still need to give it another shake. maybe there's a more minimal, intuitive gatsby setup that'll do the job.

next.js

  • next is obviously really popular and generally seems to be well-maintained. having a huge community around a project is a big plus to me.

  • it's not strictly for static gen -- it's well-known primarily as a framework for "universal" (hybrid server-/client-rendered) react apps. as noted in the react-static announcement post linked above, it seems like the static build functionality used to be a second-class citizen. but supposedly it's improved significantly since that post was written.

  • i had some tricky configuration problems with it last time i tried to use it -- but that was in full server mode, not using it as a static generator. (it also would crash when deployed in the cloud, which wasn't great -- but they fixed it in a big way, including via upstream contributions to webpack, which is great.)

  • like gatsby, it's extremely centered around plugins. this is so clumsy! instead of baking in easy configurability -- which is almost always needed -- you apply a series of kludges and hope nothing explodes. if you get desperate you can develop your own kludges, but make sure you read the (poor, not-particularly-user-facing) api guides and understand the entire project front-to-back before you attempt it.

  • dynamic routing -- that is, any routes not automatically created by looking in a pages/ directory for jsx files -- seems like a real pain, especially for static export. you need to make an exportPathMap function in next.config.js -- fine -- but then also write code in components to do the real work. for example, in their core static export example, they fetch and set up a post list in the config and then have almost identical code to do the same thing on the index page. why can't we just pass the data from the former to the latter?

  • okay, having investigated using next a little more to generate a site based on some react components and markdown files: it's definitely a mess. all the knowledge of how to do things in next is encapsulated in a directory of dozens upon dozens of examples in the main repo. this isn't necessarily bad; they work to keep them all up-to-date and there's always a paragraph that tries to contextualize the setup. but there's not always a lot of discussion of how to solve a particular problem beyond "it's so easy! use this plugin!"

    case in point: the markdown example suggests using zeit's official next mdx plugin, which makes things easy -- you can just dump mdx files alongside jsx files in your pages/ directory! -- but doesn't really provide any context for... how to do anything useful with that. presumably one might want to do more than render a plain, bare markdown file -- maybe some styling would be nice? maybe one might want to embed it in a component, yes? -- but of course there's no guidance beyond the trivial setup.

    so it looks like the next step if, say, i wanted to wrap a markdown file in a component based on a tag in the frontmatter is full-on to write a webpack loader. literally: if something doesn't work for you, write a next.js plugin to modify the webpack config, and if you can't achieve the result you need from modifying the webpack config, straight up do code generation. this seems wrong.

react-static

  • this is what we went with for the last site i helped build, but it was problematic enough that i'm actually considering ripping it out from that project and replacing it with something else to ensure better long-term sustainability. read on.

  • first, the good stuff. it has a very intuitive mental model for me. you make a static.config.js file in your project root, and in there you write a function in which you can hoover up your data, define routes (which can be dynamic), and determine what pages will be passed which slices of data. routes can be hot-reloaded when your data or your code changes.

  • i'd used it previously and had a very good experience with it.

  • newer react-static versions add filesystem-based routing on top of the config-based routing, which is potentially pretty confusing and needs extra explaining (which the docs arguably don't do a great job of). a friend noted he couldn't actually figure out how the index page was generated in the last react-static site we were working on together.

    filesystem-based routing isn't necessarily a bad idea (and it was clearly inspired by next.js) but i wonder if maybe it should've been implemented as a feature that you opt-in to in your static.config.js. this would additionally make it configurable -- as of now it seems to look for pages on a hardcoded path.

  • the new version is also buggy as hell and not well-maintained. here are some major bugs we encountered or found out about later:

    • hot-reloading doesn't work. at all. and worse than having to reload manually, after some kinds of change, everything will simply return 404 forever until your restart the server. this was a huge pain and seems like it should be a critical issue, but when we encountered it i discovered it's been an open issue for months and months.

    • adding meta tags in the document head using their recommended method doesn't actually work. they don't get generated in the static output, making them useless for opengraph and twitter previews, seo, etc. you'll probably be majorly penalized by google for not having a page title, too. we worked around this by setting static site-wide meta tags using an alternative method, but this is not a satisfactory solution.

    • it hangs when trying to run the build command. the actual build completes, but the command never exits. this stopped me from trying to set up CI since there was a high risk of it not working, but otherwise was just a minor annoyance. (EDIT: i discovered the reason for this: turns out the examples and docs were suggesting a method of watching files which might've worked fine before, but is now problematic -- another regression from behaviour that had previously worked on an older version.)

    • css modules keep breaking. this is not a dealbreaker, but it's super annoying because they were a good fit for this project. as noted in that issue, there's been a whole saga of css modules having been broken for almost a year -- attempted fixes never seem to stick.

    • we couldn't figure out why the generated bundle size was so big -- not a dealbreaker since the critical path loads super-fast and still has great time-to-interactive, but but we also weren't bringing in any huge dependencies so it was a bit confusing. it turns out it seems to be accidentally bundling big swaths of unneeded stuff on every build. uhhhhh

    • apparently it doesn't always work with npm (!) i bootstrapped it with yarn, but we also had a contributor friend who wasn't super into the js ecosystem and was using npm instead. we fortunately didn't hit this, but what the actual hell

  • oh i forgot to mention, all the code examples were deprecated at the end of last year and unceremoniously dumped into an archives/old-examples folder. they have been replaced by... nothing. the reasoning is to "decrease the use of react-static examples as boilerplates". the reason this is undesirable is unclear. but we should definitely be punished for it.

  • broadly, i think react-static has the best mental model for how data-driven site generation should work, but either they didn't deign the problem important enough or didn't have the capacity to implement any integration tests, so as soon as they started making major changes and rewriting swathes of the codebase, things started breaking in weird, horrible ways that were only discovered later.

    major, critical, long-standing bugs -- say, needing to restart the server anytime you change a content file -- make it pretty much impossible to recommend as a generator even though in theory it's my favourite of the bunch.


power rangers of web development, assemble (https://twitter.com/markdalgleish/status/1108433814647300097)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment