How I built my website new
1831 words โข 10 min read
Last year I started learning React and wanted to make a personal website so I googled for the "best" framework for creating static blog websites at that time. I chose Gatsby because its static generation features are so enticing. Next.js only has a partial support for static generation because it heavily focused on SSR at that time.
I've been using Next.js with my recent side projects and my experience has been great. So I decided why not try it with my personal website?
In this post, I'm sharing the steps I did to migrate this website from Gatsby to Next.js. Let's get started!
If you want to skip reading this and start your migration, Next.js has their official documentation about migrating from Gatsby. It's probably the best resource to get started.
You can view my pull request if you want to see the exact migration process and code changes.
๐ Also note that I was using Gatsby v2 before I migrated to Next.js v10, so features and implementation might be different and improved by the time you read this. As of June 2021, Gatsby and Next.js both supports SSG and SSR. Next.js also has a hybrid solution (ISR).
package.json
npm uninstall
The very first thing I did was to npm uninstall all Gatsby-related packages. I also removed these files from the root folder:
- gatsby-browser.js
- gatsby-config.js
- gatsby-node.js
- gatsby-ssr.js
npm upgrade
I also did an npm upgrade for the remaining packages. The important ones are react, react-dom, and tailwindcss (together with autoprefixer and postcss).
npm install
The most important package to install is next. That's it! But since I want to use TypeScript for development, I also installed typescript and @types/react. There are also other packages, mostly replacements for the Gatsby plugins, but I'll touch on that later.
Since we're now using Next.js, it's time to update the npm scripts:
old"scripts": {"build": "gatsby build","develop": "gatsby develop","start": "npm run develop","server": "gatsby serve","clean": "gatsby clean"},
new"scripts": {"dev": "next dev","build": "next build","start": "next start"}
TypeScript
In order to set up Typescript with Next.js, create an empty file named tsconfig.json in the root of your project and run npm run dev. This step is based on their official documentation.
Folder structure
static/ to public/
Next.js requires static files, like images, to be placed under the public folder in the root directory.
Gatsby supports referencing assets that are not in the static folder. I had some images under src/assets/images that I had to move to public.
With Gatsby:
my-app/
โโ static/
โ โโ images/
โ โ โโ avatar.png
โ โโ favicon.ico
โ โโ robots.txt
โโ src/
โ โโ assets/
โ โ โโ cat.png
With Next.js
my-app/
โโ public/
โ โโ images/
โ โ โโ avatar.png
โ โ โโ cat.png
โ โโ favicon.ico
โ โโ robots.txt
Files inside public can then be referenced starting from the base URL (/).
For example, to reference an image with path public/images/cat.png:
function MyImage() {
return <img src="/images/cat.png" />
}
pages/ directory
Next.js has a file-system based router built on the concept of pages.
When a file is added to the pages directory it's automatically available as a route.
The pages directory can be put in the root or even under src/ folder. It is different from Gatsby which requires you to define the pages inside src/pages.
With Gatsby:
my-app/
โโ src/
โ โโ pages/
โ โ โโ index.js
โ โโ components/
With Next.js:
my-app/
โโ pages/
โ โโ \_app.tsx
โ โโ index.tsx
โโ src/
โ โโ components/
Custom App
In order to have a shared layout components across pages, we need to have a custom app. It also lets us add a global CSS. Next.js uses a default App but to override it we need to create a file named _app.tsx under the pages directory.
_app.tsximport '../styles/globals.css'import Layout from '../components/layout'function App({ Component, pageProps }) {return (<Layout><Component {...pageProps} /></Layout>)}export default App
And this is the Layout component:
layout.tsxfunction Layout({ children }) {return (<React.Fragment><Header /><main>{children}</main><Footer /></React.Fragment>)}
Plugin replacements
In this section I'll list down the Next.js features, third-party packages, and a custom script that replaced some of the Gatsby plugins I used.
Built-in Next.js features
- next/image - Replaces
gatsby-image,gatsby-plugin-sharpandgatsby-transformer-sharp - next/head - Replaces
gatsby-plugin-react-helmet
๐จ
next/imagedoes not fully replace all the features and optimizations ofgatsby-image+gatsby-plugin-sharp. It's easier to use but it does not provide progressive images and more optimization features.If you want something like that, you probably have a image-heavy website, you can look into next-optimized-images.
MDX via mdx-bundler
This is probably the most critical part of the migration. Gatsby's MDX plugins (gatsby-plugin-mdx and gatsby-remark-* plugins) are so easy to use and to be able to recreate or improve the MDX workflow in Next.js was a challenge for me.
Good thing there are plenty of MDX solutions for Next.js to choose from:
I already tried both @next/mdx and next-mdx-enhanced in my gamedev portfolio which worked fine but I wanted to try other solutions. I chose mxd-bundler over next-mdx-remote simply because I like its API better.
I also used other plugins for these features:
- Syntax highlighting: @mapbox/rehype-prism
- Autolink headers: rehype-autolink-headers
- Slugified header id: rehype-slug
- Reading time calculation: reading-time
I didn't include the code of the mdx-bundler implementation because it'll take too much space here. Instead, you can view the exact code I use to implement it: mdx.ts, [slug].tsx
Custom scripts
- generate-sitemap.js - Replaces
gatsby-plugin-sitemap - robots.txt - Replaces
gatsby-plugin-robots-txt
For generate-sitemap.js, I included the script in the webpack section of next.config.js to invoke the function when running npm run build. The script will generate a sitemap.xml in the public/ directory.
next.config.jsmodule.exports = {webpack: (config, { isServer }) => {if (isServer) {require('./scripts/generate-sitemap')}return config},}
For robots.txt, I just created a file in the public/ directory with these contents:
User-agent: \*
Sitemap: https://yourdomain.com/sitemap.xml
Conclusion
And that's it, migration complete! As a tinkerer, I enjoyed this migration process and learned so much about each frameworks' ecosystem.
Gatsby's usage of GraphQL for transforming images, sourcing and parsing markdown files is brilliant, but I found it overwhelming at first (why do I need to learn GraphQL queries if I just want to reference my images and markdown files? ๐ค). Gatsby team has an explanation on why Gatsby uses GraphQL but I found the use cases they presented doesn't really fit mine.
On the other hand, Next.js' file-system routing and zero config makes it easy to just get started and focus on building my website without spending too much time on configuration.
Anyways, to each their own I guess. What's important is these frameworks keeps on improving and making the developer experience better which is a win for developers.
I want to thank @leerob for his awesome and open-source portfolio. I referenced his code a lot for MDX stuff when I did the migration.
I have put a lot of time and effort into rebuilding this site, as well as this blog you are reading right now. My old portfolio was built on Jekyll with the addition of a few plugins. It didn't look very nice, and it wasn't very usable either. So, I decided to change that.
The Idea
I wanted to build a website that would be easy to maintain and update. I also wanted to make it look nice and be easy to use. I also wanted to learn some new technologies and tools, so I decided to build it using Next.js and Tailwind CSS.
In June 2022 I started learning Next.js, this framework seemed the easiest for me to understand and learn. As it turned out later - it turned out to be an excellent choice.
I didn't learn from some tutorials or blogs. I just opened a code editor and started writing. I was motivated by the desire to create a new place on the Internet in which I could show my work, write blog posts, etc. I was also motivated by the fact that I wasn't happy with my old portfolio. I hated it.
Technologies I used
As I mentioned earlier - I wanted to learn something new. I used frameworks that I didn't know before or knew about them but didn't use them
Next.js
Next.js is a React.js framework that I used to build my website. It is a framework that is ideal for production, it has many useful things like image optimization, API routes, Middleware
Next.js has several options for rendering pages:
- SSR (Server-side rendering) - it will pre-render this page on each request server-side.
- SSG (Static-site generation) - it will pre-render this page at build time.
- ISR (Incremental Static Regeneration) - it will pre-render this page at build time and then on-demand.
Tailwind.css
Tailwind is a utility-first CSS framework. It provides multiple CSS classes like flex, text-center etc.
With Tailwind, you don't have to leave the HTML file to add styles to elements. Tailwind will generate the corresponding CSS classes during production and optimize them - no duplicates and no worries about load time.
Example
Tailwindpages/index.jsx[...]<a href="https://tailwindcss.com/" className="bg-blue-500 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded duration-200 inline-block cursor-pointer"><span>Tailwind</span></a>[...]
Output
output.css.bg-blue-500 {--tw-bg-opacity: 1;background-color: rgb(59 130 246 / var(--tw-bg-opacity));}// [...]
Contentlayer
Contentlayer is a content SDK that validates and transforms content into type-safe JSON data that you can use in your pages.
The easiest solution to manage content is to use a CMS (content management system), but the CMS does not give you control over all the elements you put on the site. You are limited by the elements that the CMS provides.
What if you want to build something more? Contentlayer allows you to add your own React components, and arrange the page however you want. Contentlayer is suitable for building documentation, blog, or even regular pages. Contentlayer allows you to manage all your content, it also validates it.
I chose Contentlayer for a few reasons:
- You can simply import the content as you would with any other library or component.
- It comes with built-in and configurable content validations. You don't have to worry about checking every single content anymore
- It works together with Next.js, automatically checks the content directories and renders the corresponding pages