How this Next.js blog is put together

This site is built with Next.js using the App Router. Every page is generated at build time from Markdown files and exported as a static site to GitHub Pages. I kept the setup small on purpose, so publishing a new post just means adding a file.

The stack

  • Framework: Next.js with the App Router, React, and TypeScript.
  • Rendering: Static site generation (SSG). Every page is built ahead of time, so no server is needed to serve content.
  • Content: Markdown files in public/content/. Posts are in posts/, and the home page intro is in intro.md. Each file starts with YAML front matter for the title, date, excerpt, and tags. At build time, the files are read from disk and parsed with gray-matter.
  • Styling: Tailwind CSS, plus one global CSS file for the layout, header, footer, and blog-specific pieces like the sidebar, the article text, and the table of contents.
  • Hosting: A static export, so it runs on GitHub Pages with no Node server.

One side effect of keeping the content in public/ is that the raw Markdown files are published too, and anyone can open them by URL. That's fine for a public blog, but don't put drafts there.

Layout

  • Home page: One column with the header, the intro from intro.md, a list of recent posts, and the footer. The content is at most 42rem wide, with equal padding on both sides.
  • Blog: The blog index shows the latest post. On desktop, the list of posts sits in a sidebar. On smaller screens, you reach it through the navigation. Each post has its own page at /blog/[slug].
  • Header: It stays at the top of the screen and is the same on every page. It links to Home, Blog, and Systems, and it can show a hamburger menu on small screens. When the hamburger menu is turned off, the links are centered on mobile.
  • Footer: The same on every page. It has links to Stack Overflow, GitHub, and the site's source code, then the copyright line.

How content flows

  • Posts: Each post's slug, title, date, excerpt, and tags come from its front matter. The full post is loaded by its slug when its page is built. Posts are listed newest first.
  • Intro: The home page intro comes from public/content/intro.md, which has a title and a body. Paragraphs are split on blank lines, and each one is rendered separately.
  • Table of contents: The headings in each post are pulled out of the Markdown to build a small table of contents beside long articles.

Performance

  • Static pages: generateStaticParams builds every post page ahead of time. Pages load fast, and there's no API to call when someone visits.
  • Images: The Next.js Image component is used for a few images, like the icon in the header. A static export can't use Next.js's built-in image optimization, because that needs a server. So images are served as they are (images.unoptimized: true). Keep image files small before adding them.
  • Fonts: Source Sans 3 and Source Serif 4 are loaded from Google Fonts through next/font. It hosts the font files with the site and sizes the fallback font to match, which keeps the layout from jumping when the real font loads. display: swap shows text right away in the fallback font instead of waiting.

Summary

The blog is a static Next.js site. The Markdown files are the single source of content, every page is built ahead of time, and a shared layout keeps the pages consistent. The code stays simple, so adding a post or a page is easy.