This was written on 2022, targeting Next.js 13 & the Pages router. There wasn't App router back then.
Since then I have made a few modifications and moved everything to RSC. If you're looking for help, I can share with you my solutions, but they don't warrant a full blog post.
TL;DR
Gatsby is excellent for static sites. I don’t like it for dynamic sites, and its workarounds are not ideal.
Next.js is excellent too, but recreating the same functionality (Markdown pipeline, RSS, Image optimizations) as Gatsby is a pain.
Next.js has the market share, and the backing of the React core team, so it should be future-proof.
The current stack is Next.js, Tailwind, Floating-UI, MDX, Cloudinary, and Netlify.
I've introduced some fun new features, like better snippets, static tweets, and more.
Overall the rewrite was a success, I’m happy with the result but it’s far from perfect.
Moving on from Gatsby
I've been using Gatsby since v0, and it’s perfect for my use case. That said I had a few reasons for moving on.
I mostly work with Next.js. My only Gatsby project is my blog and I don’t like the context-switching.
There’s nothing that interests me in the latest Gatsby releases. For example in Gatsby 5, the two big changes are Partial Hydration and the Slices API.
I just don’t like the GraphQL layer and its plugin ecosystem.
I can’t stress enough the last point. I wanted to use MDX in my Gatsby blog, and I was debugging some GraphQL weirdness that pushed me to rewrite it all with Next.js.
If I didn’t like tweaking, creating silly pages, and using it as a pet project, I would have just stuck with Gatsby. But I need a playground to experiment, so I decided to rewrite my blog with Next.js.
Don’t get me wrong. I’m glad I used Gatsby. It introduced me to GraphQL and allowed me to ship a very fast website to showcase my work, with little configuration. I don’t want to bash it, just state my reasons for the rewrite.
Rewriting with Next.js
I wanted to pick a framework that supports SSR out of the box. I was torn between Remix & Next.js but decided to go for the more "mature" Next.js. Had I seen the Shopify acquisition news earlier, I might have picked Remix.
I was also a bit hyped by the Next.js 13 release and wanted to try out the new features. My enthusiasm didn’t last long, as I realized it was a paper release, and the new features were still in beta or experimental. Anyhow, I decided to stick with it, use the /pages folder for now and see how it goes.
Next.js on Netlify
I have various projects and they all live on Netlify. Even if Vercel is a better fit for Next.js, I’m not going to change hosting providers. I’m also a bit stubborn, and I don’t like vendor lock-in. If Next.js starts to become problematic to deploy elsewhere, I’ll move to Remix.
So far the experience has been smooth. CSP headers were not applying correctly, but I fixed it by adding a netlify.toml file. I also cached my fonts and static assets, something Vercel supposedly does out of the box.
Here’s my config so far:
Refactoring the code
This list is not exhaustive, but it’s a good summary of the changes I made. The snippets I’ll post are meant to provide some general idea, and might not show the whole implementation. I’ll try to keep it updated as I make more changes.
1. Updating pages and components
I decided to start a new project from scratch and add TypeScript. Thankfully, I had abstracted most of the Gatsby-related stuff, so it was painless moving everything to their new folders.
2. Updating navigation
My first issue was replacing the very clever <GatsbyLink />. Unfortunately, Next.js needs some help with highlighting the active navigation link. I also had to update the prefetch functionality, so that linked pages were fetched only on hover. Otherwise, it was a simple change.
3. Nit changes
There were some smaller changes, like font loading, analytics configuration, and CSS loading. In short, whatever goes in gatsby-browser.js in Gatsby, goes in pages/_app.js in Next.js.
4. Dropping GatsbyImage, and using Cloudinary
Boy, oh boy. <GatsbyImage /> is awesome. I really didn’t have to think about images in my Gatsby blog at all. Vercel pretty much vendor locks <NextImage />, and I deploy on Netlify, so I decided to use Cloudinary and call it a day. I know Netlify does its best with its own Next Runtime, but I wanted to trust a dedicated service for optimizing my images. Here’s what I’m using:
As for the blurDataURL, I created a function to generate them on demand
..and include them in the MDX files.
Finally, I can use the <CloudinaryImage /> component anywhere in my app, including MDX files.
Could I do better? Probably. For now, it works fine and I can focus on other things. Most importantly I can focus on writing content, and if I feel inspired, build something like Kent C. Dodds' getImageBuilder.
5. Replacing Gatsby's Markdown plugins, and introducing MDX
Here’s the juicy part. I wanted to create some custom components in my markdown files. Essentially I had to replicate the functionality of these plugins:
Alright, first I created a /lib/posts.ts file with all the relevant functions to get the posts. I’ll spare you most of the implementation details. Essentially I’m reading from the file system and parsing the frontmatter and markdown body.
As for the MDX transformations, I’m using next-mdx-remote with a bunch of rehype plugins. Pretty much what Gatsby does under the hood.
I’m using Shiki for the syntax highlighting. I tried both Prism.js & Highlight.js and neither come close to it. I can use any VSCode theme, just like I was used to with gatsby-remark-vscode. Currently, I’m using the "Tokyo Night Storm" theme.
That said, I have heavily patched the rehype-pretty-code plugin. Since I’m not doing client-side highlighting, my bundle is a bit bloated, so I had fix some stuff.
Finally, consuming the data on the pages.
6. Building custom components
The process is straightforward. Write a React component..
...and add it in the MDXRenderer's list of custom components.
Then in my MDX file, I can call it as a normal React component. This makes it very easy to build fancy stuff like Charts, Code Blocks, DataTables, or some silly stuff I’m planning next.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ultrices faucibus massa, vitae placerat leo lacinia non. Integer malesuada velit in magna luctus sodales sed eu justo.
7. RSS
My RSS implementation makes me sad. At this point, I contemplated if writing my blog in Next.js makes any sense. I’m using this approach. This will be improved in the future.
Conclusion
Was it worth it?
My previous stack wasn’t broken, so it didn’t need fixing. There isn’t any wow moment, I just had to scratch that itch. I’m happy with the result, even though it feels very hacky at various places, but I’m sure I’ll improve it moving forward.
If anything, I’m particularly happy with the bundle size decrease, as well as my fancy new code blocks.
I shouldn’t be hard on Next.js, as I’m building a blog and not a complex app. If anything I make things harder for myself by not using a CMS.