Migrating from Gatsby to Next.js
Recently — and quite frankly I’m shocked this wasn’t a major national news story — I was bored. As I often do when I’m bored, I decided to tinker with this here website. There weren’t any obvious bugs to fix and the design is pretty much sorted, which unfortunately left very little to tinker with. All hope was not lost, as I have been curious about Next.js’s static site generation since it debuted with version 9.3 of the framework. This seemed an opportune time to test it out independent of content, design, or site structure. This also meant moving away from Gatsby, which I have been using as the basis for this site for the last 3 years.
If you’re unfamiliar, Gatsby and Next.js are frameworks for developing highly-performant React-based websites. They share some things in common, but differ in core philosophy: Gatsby is a static site generator first and foremost, while Next.js is a more general server-rendered framework for React applications. While both offer the ability to generate static pages, only Next.js has the ability to generate server-rendered pages on the fly (though, contrary to popular belief, Gatsby sites can have client-only routes). The other main difference is their approach to data-fetching: in Gatsby, all data is accessed via an internal GraphQL API while Next.js is completely agnostic as to how the data finds its way to your components.
Nothing.
Well, almost nothing. As Jared Palmer expertly points out, the unified data graph approach of Gatsby often introduces more complication than is necessary for small static sites such as this. (You might find yourself asking “isn’t any React framework more complication than is necessary for small static sites such as this?” And to that I say yes. But this site is just as much a portfolio piece as a practical bit of code and also do you hate fun?)
But ultimately this was an excuse to dip my toes in the Next.js waters. I only fully dived in when it turned out the temperature was to my liking.
The Next.js docs have a handy migration guide, which seemed as good a place to start as any. Following these steps got me most of the way there, but I needed to do some work to get the site across the finish line. A bulk of that work was replicating functionality that plugins were providing in the Gatsby version of the site. There are Next.js plugins, but for the most part extra functionality is left up to the user. Here’s some of the notables:
Like I said, Next.js does have plugins. One of them is for MDX. Following the instructions for the @next/mdx
package got the files compiling. Since Next.js doesn’t handle frontmatter directly, I had to convert the frontmatter for each file to an object and manually export the template with the data. I could have used getStaticPaths
for this, but rendering the files in place felt the best.
An underrated aspect of Gatsby is the ability to format dates at build time without installing any packages or doing any date manipulation on the frontend. Luckily, libraries like date-fns
make manipulating dates in JavaScript fairly easy (standard library when?). Combining parseISO
with format
got me date strings for my blog post headers without any timezone wonkiness.
Setting up was Theme UI was actually the first thing I did, since literally every component on the site would look like hot garbage without the ThemeProvider
. Because I would need to wrap every page of the site in the provider, I created a custom App
to do so. This also allowed me to wrap every page with my Layout
component (à la Gatsby v1), meaning I didn’t have to manually include it in every page.
import * as React from 'react'import { ThemeProvider } from 'theme-ui'import theme from '../constants/theme'import components from '../components/MDXComponents'import Layout from '../components/Layout'const App = ({ Component, pageProps }) => (<ThemeProvider theme={theme} components={components}><Layout><Component {...pageProps} /></Layout></ThemeProvider>)export default App
With just the ThemeProvider
, there will be a flash of the default color mode on initial page load, which can be jarring for users who are using an alternative color mode. Luckily, Theme UI is aware of the problem, and offers a handy InitializeColorMode
component to solve for it. In Next.js, this is used in a custom Document
:
import * as React from 'react'import Document, { Html, Head, Main, NextScript } from 'next/document'import { InitializeColorMode } from 'theme-ui'class MyDocument extends Document {render() {return (<Html lang="en"><Head /><body><InitializeColorMode /><Main /><NextScript /></body></Html>)}}export default MyDocument
The Link
section of the migration guide doesn’t quite paint the full picture. There is one significant difference between Gatsby and Next.js’s Link
components: the latter isn’t always an <a>
element. If the component’s child isn’t a text node or itself an <a>
, Link
just adds an onClick
handler to the child and calls it a day. This means that I couldn’t use Theme UI’s as
prop to copy the functionality, and that I needed to add a passHref
prop to each Link
to force Next.js to add an href the <a>
rendered by Theme UI’s Link
. I didn’t want to do this for every link on the site, so I made a custom Link
component to combine the two:
import * as React from 'react'import { default as NextLink } from 'next/link'import { Link as ThemeUILink } from 'theme-ui'const Link = ({ href, ...props }) => (<NextLink href={href} passHref><ThemeUILink {...props} /></NextLink>)export default Link
I’m one of the dozen or so people that continued to use an RSS app after Google decided to off Google Reader, murder-style. Getting a working RSS feed was by far the most challenging part of this process. I nearly gave up on the conversion altogether because of it — and I’m still not very happy with where I ended up with it. After failing to get my MDX to compile in a custom node script, I brute forced it and extracted the meta
object (frontmatter) from the MDX files by way of regex. This means my feed no longer contains the full contents of the article, which is a bummer. Maybe I’m missing something obvious here. Maybe this is a solved problem. But I wasn’t able to find an example of a statically-rendered Next.js site using MDX directly that had an RSS feed, and I didn’t want to be held up on this (I estimate approximately negative 3 people are subscribed to the feed here).
Luckily, the rss
package made actually generating the feed super easy once I had the data. I added a postbuild
script to my package.json
that generates a sitemap and an RSS feed every time the site is built.
Gatsby has a really handy plugin to use Preact in production, reducing your bundle size by a fair amount. If your Gatsby site doesn’t need to support IE10, I highly recommend using it. While there isn’t a plug-and-play option for Next.js, we can use npm aliases to replace references to React with the preact/compat
package. Combined with some webpack wizardry, we can reduce the size of our builds a good amount. Here’s the official example that I definitely didn’t just copy & paste from.
While I’m happy with the outcome of this exercise, I can’t possibly answer that. If your Gatsby site is working, and working for you, I would err on the side of “no”. This is doubly true if your site has multiple sources of data: working with multiple source APIs is Gatsby’s true strength (or at least it’s where the unified graph approach to site building shines brightest). That said, I was clearly happy enough with the early results of the exercise to put further time into getting the site into shipping state. Here are some reasons why:
I’m pretty adamant about keeping my dependencies up to date. Moving on from the plugin-centric Gatsby approach to the userland Next.js approach meant I got to remove a lot of those dependencies. Like, a lot a lot. Like, my package-lock.json
lost 15 thousand lines of code a lot. Some of this was surely due to my having too many plugins (this site has exactly one image on it at time of writing, gatsby-image
was probably overkill). But a lot of it has to do with Next.js managing internally a lot of things that Gatsby leaves to plugins.
Doing things the Gatsby way means pulling your data out of the unified GraphQL API. As mentioned earlier, that’s great when you have lots of data sources, but less so when you’re working on a tiny blog like this. Plus, since getStaticProps
runs at build time, you can move some costlier work there to boost production performance. You can do similar things with Gatsby, of course — but not as simply.
If I ever wanted to create a truly dynamic page, using Next.js allows me to do that. For example, the homepage currently fetches new stats on mount, and updates them when the request completes. Moving that work to a server could reduce page shift, while continuing to ensure the stats are always accurate (doing so would require me to change hosting providers, but that’s a can of worms for another day).
I’ll do a lot of tinkering when I’m bored.