Migrating a Markdown Blog From Gatsby to Next.js
I recently migrated a Markdown Gatsby blog to Next.js. Although there’s a lot to consider and it takes some time, it’s not as scary as it sounds.
In this post, I will share some lessons you might find useful for facing the same challenge.
There’s an official guide on doing the migration in Next.js docs, which I somehow completely missed it and migrated by myself. You might find it handy.
Swapping the Old With the New
First things first, you need to uninstall Gatsby from your project.
Keep the list of plugins you used in Gatsby somewhere close. You will need to recreate that functionality in Next.js.
Start by removing Gatsby dependencies and scripts
section in your package.json
.
{
"scripts": {
- "start": "gatsby develop",
- "build": "gatsby build",
- "serve": "gatsby serve",
- "clean": "gatsby clean",
},
"dependencies": {
- "gatsby": "^4.11.1",
- "gatsby-plugin-sass": "^5.11.1",
- "gatsby-plugin-sharp": "^4.11.1",
- "gatsby-plugin-sitemap": "^5.11.1",
- "gatsby-remark-autolink-headers": "^5.11.1",
- "gatsby-remark-images": "^6.11.1",
- "gatsby-remark-prismjs": "^6.11.0",
- "gatsby-remark-smartypants": "^5.11.0",
- "gatsby-source-filesystem": "^4.11.1",
- "gatsby-transformer-remark": "^5.11.1",
"react": "17.0.2",
"react-dom": "17.0.2",
}
}
Do not remove react
and react-dom
since Next.js depends on them as well.
Now you can install Next.js.
npm i next
And, add the Next.js scripts.
{
"scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
},
"dependencies": {
+ "next": "^12.2.5",
"react": "17.0.2",
"react-dom": "17.0.2",
}
}
You can run npm run lint
to make Next.js set up ESLint, if you don’t have your own configuration already.
If you weren’t using TypeScript before, this is a great opportunity to add it to your project. Next.js has great TypeScript support out of box.
Create a tsconfig.json
file in the root of your project.
touch tsconfig.json
Now, run npm run dev
and follow the instructions in your terminal.
If something doesn’t work out with the dependencies, you can try deleting your node_modules
and package-lock.json
and running a fresh npm install
.
Cleaning up Files
Now that Next.js is installed, you can clean up some files that Gatsby was using.
Start by deleting .cache
and public
folders.
Next.js uses the public
directory as well, but for static assets. If your site uses images, you will need to put them all inside public
and update all image source paths.
Otherwise, you can read a post I’ve written on how to keep images alongside Markdown files.
.gitignore
Update .gitignore
by removing .cache
and public
and adding .next
- .cache
- public
+ .next
Metadata
Create a file, for example src/site-metadata.json
, for your website’s metadata and copy the data from siteMetadata
in gatsby-config.js
.
{
"siteUrl": "https://site-url.com",
"title": "Site Name",
"description": "Site description."
}
At this point you can either delete gatsby-config.js
keep it somewhere as a reference. I opted for keeping all gatsby-*.js
files in a folder named gatsby
until I migrated everything to Next.js.
Migrating Pages
Similarly to Gatsby, Next.js also uses pages
folder to automatically create pages for you. So you don’t have to change much for pages with simple paths, like pages/about.js
. Strip out all the GraphQL and grab your data from the site-metadata.json
file your created previously.
The pages that you create programmatically using createPages
API in gatsby-node.js
need to be completely remade. You will need to use Next.js static site generation (SSG) feature.
In Next.js, to create pages dynamically, you set up a file for a dynamic route like pages/post/[id].js
. Then you use getStaticProps
and getStaticPaths
functions inside [id].js
to generate pages for each id
route parameter programatically.
You will have to do content sourcing by hand inside getStaticProps
function. At first, I used Node’s filesystem module and extracted content from Markdown files by hand. I soon learned about Contentlayer and used that instead. It will save you a lot of time.
You can check out my tutorial on how to create a Next.js blog with Contentlayer.
After migrating pages, you can delete gatsby-node.js
.
Templates
Although in Next.js you don’t need the templates used in Gatsby, you can turn them into plain components and reuse them.
For example, you can reuse the template for a post page in Next.js, just strip away the GraphQL queries and other Gatsby stuff. Now you can import the component in the pages/posts/[id].js
page and use it.
Migrating remark and rehype Plugins
This is the biggest pain when it comes to migrating from Gatsby to Next.js. Gatsby does a good job at abstracting the entire unified ecosystem with its own plugins. You will have to learn about unified, remark and rehype and recreate the Gatsby plugin functionality yourself.
Here’s where Contentlayer can help you again. It has unified running underneath, which allows you to drop in remark and rehype plugins easily.
You can attach remark and rehype plugins in Contentlayer’s config like so.
{
remarkPlugins: [remarkSmartypants],
rehypePlugins: [
rehypeSlug,
rehypeAutolinkHeadings,
rehypePrism,
],
}
PrismJS
If you used gatsby-remark-prismjs
, you can use rehype-prism-plus
in Next.js instead. But, your syntax highlighting will most likely break, because it uses different class names for styling.
You will need to adjust your PrismJS CSS stylesheet and substitute all the .gatsby-highlight
classes with ones that rehype-prism-plus uses.
Updating Links
Another similarity between Gatsby and Next.js, is that they both use their own component for links.
Go through your project and switch all Gatsby’s Link
components to next/link instead.
Next.js Link
component is a bit different:
- Use
href
instead ofto
prop for URL - It has to contain only one child element that must be
<a>
- You can put other elements inside the
<a>
element - Classes for styling go onto the
<a>
tag
<Link href="/posts">
<a className="link">Go to posts</a>
</Link>
Adjusting Styling
In Next.js, global styling imports is done inside pages/_app.js
.
Move your imports from gatsby-browser.js
to _app.js
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
If you use CSS modules, the class names won’t work anymore if they include hyphens.
.css-class {
/* ... */
}
In Gatsby css-class
was automatically mapped to cssClass
, but in Next.js it no longer worked for me.
<h1 className={styles.cssClass}>Welcome</h1>
It threw an error instead.
So, use cssClass
format inside CSS Modules.
.cssClass {
/* ... */
}
After this, you can delete gatsby-browser.js
.
Adding Sass
Next.js has great built-in support for Sass. So it shouldn’t be any trouble to use it instead of plain CSS.
Nothing also stops your from using SCSS with CSS Modules in Next.js,
Sitemap
To create a sitemap in Next.js, you can use the next-sitemap package.
In my case, the sitemap file was changed from sitemap-index.xml
to sitemap.xml
.
Redirects
The redirects syntax differs from Gatsby a little bit. You will need to adjust it if you use wildcard redirects.
I moved all my redirects into a redirects.json
file.
[
{
"from": "/blog/:slug*",
"to": "/posts/:slug*"
}
]
And here’s how you can set them up in next.config.js
.
const redirects = require('./redirects.json');
module.exports = {
async redirects() {
return redirects.map(redirect => {
return {
source: redirect.from,
destination: redirect.to,
permanent: true,
};
});
},
};
SEO
In Gatsby, your go to tool for setting up SEO is React Helmet. This package is no longer needed in Next.js, you can rewrite your SEO component using next/head.
You can also use next-seo if you want, it has good feedback, but next/head
was enough for me.
Keep an eye on your Google Search Console, you might even drop some traffic, but it’s nothing you can’t fix. Mine went back to normal pretty quick.
Since I had some trouble with URL trailing slashes after migrating from Gatsby, I added canonical URLs to my pages. This helped stop duplicate content warnings from Google.
Structured Data
When you use structured data in Next.js, it needs to be set using dangerouslySetInnerHTML
, otherwise quotation marks used in the script
element are escaped.
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
Use Schema.org validator on your live site to check if its working.
Images
Next.js comes with a component that optimizes your images out of box. You don’t need any plugins that you used in Gatsby. Just use the Next.js image component.
Like mentioned before, images must be stored in public
directory in Next.js. Their source path is relative to the public folder. If you put an image inside public/posts/first-post/image.jpg
it’s source path should be /posts/first-post/image.jpg
.
Lazy Loading in Markdown
There is no clear go-to way when it comes to lazy loading images inside Markdown files in Next.js. I decided to add MDX support to my project and create my own component that uses next/image
.
Here’s a post I’ve written on how to use Next.js Image component inside Markdown.
Summary
To summarize the process, here are the steps it takes to migrate from Gatsby to Next.js:
- Delete Gatsby dependencies
- Install Next.js and optionally TypeScript
- Re-implement pages using Next.js SSG
- Set up unified ecosystem and add remark and rehype plugins
- Update all links
- Update global CSS imports
- Update sitemap generation and adjust your Google Search Console settings
- Update redirects
- Get rid of React Helmet and use
next/head
, pay attention to structured data - Adjust your images
Good luck!