Coding

How I Migrated My Website from Gatsby to Next

I migrated my personal site to Next.js and I'm not going back!

By Fabian Lee | July 14, 2021

7 min readLoading views

How I Migrated My Website from Gatsby to Next

Why Switching to Next.js?

  1. Level up myself
  2. Flexibility in choosing page rendering method
  3. Implement API services with ease

The Stack

  • Next.js
  • TailwindCSS
  • MDX
  • Firestore
  • SWR
  • Vercel
  • sal.js

How's the Process?

Components

All components can stay the same as they were. I only replaced all of the Gatsby Image and Link components to Next.

Dark Mode

I had to hack a script in html.js and create my own ThemeProvider to get it working smoothly in Gatsby. Josh Comeau has another way of doing it in Gatsby. Anyways, I had to add a significant amount of code to achieve a simple UI change.

next-theme made it so easy and it works perfectly with TailwindCSS simply by setting the attribute prop in its ThemeProvider that match with the dark mode settings in tailwind.config.js.

Here's the example:

tailwind.config.js
module.exports = {
  //...
  darkMode: 'class'
}

_app.js
export default function App({ Component, pageProps }) {
  return (
    //  Tells next-theme to toggle the class attribute in the root html
    <ThemeProvider attribute="class">
      <MDXProvider components={MDXComponents}>
        <Component {...pageProps} />
      </MDXProvider>
    </ThemeProvider>
  );
}

Replacing Gatsby Plugins

Gatsby plugins are sort of like Wordpress plugins where you install what features you need. Next is fundamentally a Node application so I had to install individual libraries to set them up explicitly.

gatsby-source-* souring plugins became API fetches or fs reads in getStaticProps to retrieve all the content.

Here's an example:

pages/index.js
const Index = ({ blog, bookNotes }) => {
  // Map through 2 arrays to list out the latest 3 posts
}

// Runs at build time
export const getStaticProps = async () => {
  const blog = await getAllFilesFrontMatter('blog').slice(0, 3);
  const bookNotes = await getAllFilesFrontMatter('book-notes').slice(0, 3);
  return { props: { blog, bookNotes } };
};

gatsby-plugin-mdx along with a bunch of remark plugins are replaced by next-mdx-remote, gray-matter and original remark libraries.

// Parses the mdx file get the content and frontmatter
const { data, content } = matter(source);
// Further parses the content with remark plugins
const mdxSource = await serialize(content, {
  mdxOptions: {
    remarkPlugins: [
      require('remark-slug'),
      [
        require('remark-autolink-headings'),
        {
          linkProperties: {
            className: ['anchor'],
          },
        },
      ],
      require('remark-code-titles'),
    ],
    rehypePlugins: [mdxPrism],
  },
});

Alias import and Head component are provided by Next out of the box.

I had to write a custom scripts to generate sitemap and RSS Feed. The basic idea is just reading all the page files and map them to the corresponding XML format. And I had to add robots.txt manually.

MDX Posts Styles

I did all the MDX posts styling manually and pass them to MDXProvider in Gatsby.

MDXComponents.js
export default {
  h1: (props) => <h1 className="text-5xl font-bold mb-8 mt-12" {...props} />,
  h2: (props) => <h2 className="text-4xl font-bold mb-7 mt-11" {...props} />,
  h3: (props) => <h3 className="text-3xl font-bold mb-6 mt-10" {...props} />,
  h4: (props) => <h4 className="text-2xl font-bold mb-5 mt-9" {...props} />,
  h5: (props) => <h5 className="text-xl font-bold mb-4 mt-8" {...props} />,
  h6: (props) => <h6 className="text-lg font-bold mb-3 mt-7" {...props} />,
  p: (props) => <p className="mb-8 leading-7" {...props} />,
  a: (props) => <Link {...props} />,
  ul: (props) => <ul className="list-disc pl-4 mb-8" {...props} />,
  ol: (props) => <ol className="list-decimal pl-5 mb-8" {...props} />,
  li: (props) => <li className="leading-7 mb-3 pl-1" {...props} />,
  inlineCode: (props) => <Code {...props} />,
  blockquote: (props) => <Blockquote {...props} />,
};

Glad I found @tailwindcss/typography to help me skip all these customizations. With some extra configuration for dark mode styles, I simply needed to add prose class to style my posts automatically. Though it comes with a drawback of overriding all your custom components styles inside any MDX files. I had to add an unprose class to disable some styles.

Edited: TailwindCSS 3.0 finally added a not-prose class option for us to achieve the same results!

Deployment

I moved from Netlify to Vercel simply because Next is backed by Vercel which should have the best optimization. They both have auto preview and production deployment when you make a pull request and merging into your main branch. Netlify requires you to install a plugin to enable Next's dynamic features like dynamic routes and APIs though.

Enhancements

I added a page views feature for my posts using Firebase Firestore. And I use swr to do client-side data fetching.

Views.js
const Views = ({ slug }) => {
  const { data } = useSWR(`/api/views/${slug}`, fetcher);
  const views = new Number(data.total);
  return <p>{`${views} views`}</p>;
}

API route for getting the views from Firestore:

pages/api/[slug].js
export default async function handler(req, res) {
  if (req.method === 'GET') {
    const postRef = await db.collection('posts').doc(req.query.slug);
    const doc = await postRef.get();
    const data = doc.data();

    return res.status(200).json({ total: data?.views || 0 });
  }
}

Another minor improvement would be adding sal.js for scroll animations. It's a lightweight library that utilizes the IntersectionObserver API.

Regarding performance, I got almost perfect Google Lighthouse scores. Next enforces certain rules with images that helped my Cumulative Layout Shift (CLS) performance and accessibility. Gatsby was great too overall. It's my OCD kicking in to push those numbers up.

Lighthouse Score
Lighthouse Score

What I've Learnt

Gatsby vs Next Image

I like that Gatsby has built-in support of image placeholder for all static and dynamic images while you have to implement the blur placeholder yourself with third-party libraries in Next. I used plaiceholder to generate base64 blurred images.

But I do understand while Next skips handling dynamic images means a shorter build time.

TailwindCSS Prettier Plugin

Styling with TailwindCSS can make your className prop messy. I found a Prettier plugin which helps sort all your Tailwind classes. You have to use it.

Less Blackbox-like with Next

Gatsby can get your static site up and running fast combined with different plugins. The ecosystem tends to hide all low level settings away. You will also need to learn GraphQL and Gatsby's lifecycle in order to start doing bits of customization.

Both Next and Gatsby are opinionated frameworks. But Next is less-opinionated comparatively. Next mainly hides away all the routing and bundling part. Next can minimize all setup hassels and let us focus on the core product. With your React and Node knowledge, you can basically do whatever and however you want in Next.

Content Security Policy

It's a shame that I haven't looked into CSP in Gatsby. Security wasn't in my checklist. It's an essential topic to secure my site since I'm implementing API services. You can test out your site's security here. You should see what security measures you are missing. Use the report as a reference and setup your CSP accordingly. Ace it now!

Security Report Summary
Security Report Summary

Developer Experience is way better in Next

Gatsby occasionally breaks when it recompile my MDX file changes. Having to use next-remote-watch for writing irons out the process. The build and deployment time are also significantly faster in Next.

Even if I have thousands of posts, I can always use the fallback: true option in getStaticPaths to generate posts on the fly without having to pre-render all of them every time I make a change to my site.

I spent less time reading documentations in Next than in Gatsby. As I said before, React and Node knowledge are primarily all you need to start building!

Thanks For Reading 😉

If you would like to show your further support:

Buy Me a Book