Technical blog by Matt Kubej

Blog Redesign with Next.js

June 18th, 2021

When I initially started this blog I built it with Gatsby along with one of their blog templates, which fit my needs very well. However, I have been wanting to spend more time with Next.js, especially as I have continued to see Vercel push out more updates. So, I took the opportunity to rewrite my blog with Next.js, which in turn gave me more control over the implementation and roll my own style. However, I do not plan to open source my blog at this time, but this post will outline how I went about it.

What is Next.js?

Next.js offers itself as a React framework, which supports static site generation (SSG) and server-side rendering (SSR). It requires little to no configuration, page-based routing, and offers a number of great optimizations tailored towards performance, which suits itself well for a blog.

For a more in-depth overview, I would recommend reviewing the Next.js documentation.

Building a blog

From my previous blog with Gatsby, all my posts were stored as Markdown (.md) files, which Gatsby sourced from to statically generate the site. Next.js can be configured in a similar manner, which makes for an easy transition. Vercel has a blog-starter example, within their Next.js repository, which provides a great example of how to accomplish this with their solution.

I followed a similar approach as outlined within their example, but I desired to use MDX instead of Markdown for my blog posts. MDX provides the ability to introduce JSX into your Markdown. However, the example provided by Vercel only supports Markdown.

This can be accomplished with a few refactorings to Vercel's blog-starter and a couple of additional npm modules. The blog-starter effectively works by having the path name of a post, also referred to as a slug, reference a Markdown file to render on the site. The contents of the Markdown file are fed to remark, which in turn outputs html. In order to make the switch to MDX, we can replace remark with next-mdx-remote and mdx-prism.

Rather than sourcing from .md files within getPostBySlug, we can adjust it to source and serialize .mdx files to an input format that can be consumed by the <MDXRemote /> component from the next-mdx-remote project for rendering. Below is an example of how this can be accomplished.

export async function getPostBySlug(slug: string) {
  const postPath = path.join(postsDirectory, `${slug}.mdx`);
  const postContents = fs.readFileSync(postPath, 'utf8');
  const { data, content } = matter(postContents);

  const mdxSource = await serialize(content, {
    mdxOptions: {
      rehypePlugins: [mdxPrism]
    }
  });

  return {
    mdxSource,
    frontMatter: {
      slug,
      ...data,
    },
  };
}

Within the Vercel example, getPostBySlug is invoked within the getStaticProps Next.js function of the slug dynamic route. The getStaticProps function fetches data at build time of the page, which then passes the return value's props attribute to the top-level page component as props. Before returning the output of getPostBySlug within getStaticProps, the example converts the Markdown to HTML with markdownToHtml, which ultimately gets passed down as a prop to the component. The top-level component renders this content within a child component as seen here.

Due to the refactor of getPostBySlug above, which serializes the MDX content to an mdxSource format that the <MDXRemote /> component can render, we can refactor getStaticProps as follows.

export async function getStaticProps({ params }) {
  const post = await getPostBySlug(params.slug);

  return {
    props: {
      ...post,
    },
  };
}

The mdxSource now flows to the top-level component as a prop, which can in turn be passed to the <MDXRemote /> component for rendering. A trimmed down top-level component could look as follows.

export default function Post({ mdxSource }) {
  return (
    <article>
      <MDXRemote {...mdxSource} />
    </article>
  );
}

With these small changes in place, the Next.js blog-starter should be able to render MDX.

Deploy

After converting my blog to Next.js, I needed a means of deploying it, so people could access it. With my original implementation using Gatsby, I used a Gatsby plugin, which effectively dumped the statically generated assets to an AWS S3 Bucket. I considered a similar approach for this blog, but recently saw that AWS Amplify offered Next.js integration. Even though Amplify would not be free, it did offer SSR and would continue to be in the AWS ecosystem with my other solutions. However, if you are looking for hosting, Vercel offers a very generous hosting solution, which is free if you are a hobbyist.

AWS does have a blog post, which outlines how to host a Next.js SSR application with AWS Amplify, which I tried. However, I quickly encountered issues and discovered that AWS Amplify did not support Next.js 10 SSR at the time. Mind you this was over a month ago, so AWS may have updated their solution. Due to my blog not requiring SSR, I continued to use AWS Amplify, but deployed as SSG. This was accomplished with AWS Amplify's simple web application wizard for setup, which wired up to my GitHub repository. Now on merges to my main branch, AWS Amplify runs a build and deploys my changes. Overall, AWS Amplify required little effort to publish my blog and has made releasing changes effortless.