How this blog worksBuilding a blog with Next.js
With this post, I want to share how this blog works. If you are interested in how to build your own blog from scratch using Next.js, this post is for you. It stays on a high level, so no prior knowledge of Next.js is needed.
At the beginning of this year, I decided to start my own blog. Besides the obvious motive of sharing my thoughts and building an audience, I was also eager to build a new website. This is why I did not simply go for a blog platform and did not use existing blog software. I love building for the web. Therefore my arguments for creating this blog from scratch are the following:
-
It is a huge learning opportunity with which I can advance my web skills, like Next.js, React, UI design and CSS. It also forces me to delve into an area I was interested in for a while, but couldn't gain much experience with yet: search engine optimization (SEO).
-
Related to the learning is the tech playground argument. With my own blog solution, I have one productive side project more in which I can try out new technologies or features. I don't want to risk missing out on audience by breaking anything though, so I am careful in that regard.
-
Self-made solutions have the greatest flexibility. I can decide which features to provide in blog posts, e.g. code samples in which I can highlight individual lines. And if I want to add more functionality to the website besides the blog, I can do this in any direction I like.
-
In contrast to blog platforms, I keep full control over my content. I always know where it is stored and in which way it is presented.
Naturally, the cost for this is the effort to build and maintain everything on my own. I feel that the benefits outweigh this cost though. And since I anyway want to write about web development, a big part of the blog building actually serves as research and inspiration for new blog posts.
In the following, I give an overview of the blog's tech stack and how it works. I want it to be useful for a wider audience, so I will stay on a high level and I don't assume prior knowledge of the specific technologies.
Recently I made the source code of this blog available on GitHub, so feel free to check it out for a look into the details.
Tech stack
First, a brief overview of the technologies and tools I use for the blog:
- Language: TypeScript
- Framework: Next.js, React
- Hosting: Vercel
- Content source: GitHub
- Content processing: unified
- Styling: Tailwind CSS
- Package manager: pnpm
- Analytics: Plausible
Framework
This blog is built with Next.js, a common full-stack framework for web apps. Besides its general support for web apps, Next.js features static site generation (SSG). With this mechanism, static web pages are generated at build time and are then deployed to a content delivery network (CDN), bringing the pages close to the user. This is ideal for websites on which page load performance is a priority, while the content does not change very frequently–like for blogs.
Next.js is based on React, my go-to solution for building UIs on the web. In React, I love how easy it is to create components using the JSX syntax. The simplest way to use React is to render the web app on the client-side (typically with Create React App). For a better user experience and SEO however, frameworks like Next.js pre-render the React components using SSG and other mechanisms.
When building for the web, I always use TypeScript instead of bare JavaScript. TypeScript makes me much more productive: It shows problems in the code at development time and makes refactoring much safer. Without it, a lot more manual testing or unit tests would be needed for any change in the code. TypeScript has become very common in web development; React and Next.js both support it out-of-the-box.
Content source
Like it can be expected in tech, the blog posts are written as Markdown. Metadata like the date and title of a post is added as front matter—this is a block of YAML on top of the Markdown file; a common practice with static site generators.
---
date: 2022-05-07
lastModified: 2022-05-07
title: Building a blog with Next.js
kicker: How this blog works
…
---
## This is a heading
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
…
The Markdown files are hosted on a separate GitHub repository, the content repository. The blog reads the files from that repo using the GitHub API.
Using Git for the blog content gives me a versioning system with which I'm familiar and which is well supported in any editor. Especially for correcting or slightly extending posts afterward, a versioning system is an indispensable quality assurance tool.
A quick recommendation on the side: When working with Markdown in a Git repo, it
is crucial to stick to a maximum line length (e.g. 80 characters). If the lines
get longer than a typical editor window is wide, reviewing changes on Markdown
files is a huge pain. Therefore I always use Prettier
and the
Prettier extension
for Visual Studio Code (generally, but also when working with Markdown). In the
Prettier configuration, I set the prose-wrap
setting to always
and in VS
Code I enable the
Format on Save
feature. This way, the lines are automatically wrapped whenever I save a
Markdown file, and I never have to think about it anymore.
Besides the Markdown files with the post contents, I also store all assets used in the posts in the content repo (mostly images). I do this so that I have everything belonging to a post in a single place, as the master location. For hosting the images, I upload them to the image CDN ImageKit.io and link to the URLs provided there in the post's Markdown.
Content processing
How does the Markdown source of a blog post end up as a web page? For this task I use unified, an ecosystem of packages for transforming prose content between different formats.
A simple but limited solution to rendering Markdown would be to transform the
Markdown to HTML and then directly display the HTML. However, for some elements
in the blog posts, I want to provide features that would be hard to achieve just
using raw HTML. Therefore, I want to map these elements to React components
which are then rendered to the DOM. For example, I want to use the
Next.js Image component with
its out-of-the-box image optimization—instead of raw HTML img
elements. And
for code samples, I want to have syntax and individual line highlighting, for
which I built
my own component based on highlight.js.
Using the pipeline concept of unified, I define the process for this transformation from Markdown to React nodes. It looks something like this:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeSlug from "rehype-slug";
import rehypeReact from "rehype-react";
async function markdownToReact(markdown: string): ReactElement {
return await unified()
.use(remarkParse) // Parses the Markdown source into an abstract syntax tree
.use(remarkRehype) // Transforms the Markdown into HTML
.use(rehypeSlug) // Adds id attributes to the headings for the anchor links
.use(rehypeReact) // Transforms the HTML into React nodes
.process(markdown);
}
This code sample is simplified to show how unified is used. The
actual pipeline
is split up into two parts and has a few more plugins. The reason for the
splitting is that the last part of the transformation, rehypeReact
, needs to
be able to run additionally on the client side for the so-called hydration
and for subsequent (non-initial) page loads. Most of the transformation however
exclusively runs at build time.
An alternative to using unified to transform from Markdown into React components could be to use the tooling of the MDX format. MDX is a superset of Markdown, which allows using React's JSX syntax within Markdown. The tools provided for MDX can then transform an MDX document into React. I haven't tried this approach though and can't say if it would have advantages over the unified-based solution. Even though the tooling might be useful, I did not want to use MDX as a format for the blog posts. I don't need and want the blog content to be dependent on React components.
Further tools and services
Hosting
The blog is hosted on Vercel. This hoster is the company behind Next.js. Vercel provides a great developer experience: For example, it seamlessly pulls the code from GitHub, and for pull requests, preview deployments are automatically created. Since it is also free for non-commercial projects, it is an obvious choice. Alternatively, Next.js could be hosted on any other provider where Node.js or Docker is supported—so there is no vendor lock-in with Next.js.
Styling
For styling the blog, I use Tailwind CSS. This is
a utility CSS framework, providing atomic CSS classes like font-bold
,
text-blue-300
, or pt-6
. These classes mostly only set a single CSS property
each, e.g. pt-6
sets a padding-top
of 1.5rem
(number 6
on a
pre-configured scale of spacings). I have been using Tailwind CSS for several
years now and it has become my favorite solution for styling web apps. It
enables rapid styling that is easy to change later, without the cognitive
overhead of defining or refactoring a CSS class system. If you would like to
understand better what the reasoning behind this approach to styling is, I can
recommend reading through
CSS Utility Classes and "Separation of Concerns" by Adam Wathan
(one of the Tailwind CSS creators).
Package manager
Instead of the default package manager of Node.js, npm, I use the faster and more efficient pnpm. So far, it works well for me. Currently, there is one minor issue with Prettier in combination with pnpm though: The automatic loading of Prettier plugins does not work. Plugins, therefore, have to be specified explicitly in the Prettier config file. I stumbled upon this issue because I use the Prettier plugin for Tailwind CSS.
Analytics
For analyzing traffic on the blog I use Plausible Analytics. It is a simple, privacy-focused alternative to the market leader Google Analytics. The special feature about Plausible is that its usage is compliant with privacy regulations like GDPR, without the need for an annoying cookie banner. Furthermore, it is open-source and all data is hosted in the EU. It does not have a free plan for small sites, but the pricing is very transparent and a single subscription can be used for multiple sites.
Conclusion
So far I am happy with this tech stack. For now, I kept the UI design very simple (and actually used mostly Tailwind UI components). I can imagine redesigning the UI of the blog in the near future when time allows.
Let me know on Twitter if you have any comments or questions on how this blog works, I'm happy to share! And if you want to build a blog like this too, feel free to use the code on GitHub as an inspiration.