How to Setup a Blog with Gatsby, TailwindCSS, and Markdown

Creating a blog today is easier than ever, thanks to the power of modern web development tools. In this guide, we'll explore how to set up a blog using Gatsby, a React-based static site generator, alongside TailwindCSS for styling and Markdown for content management.

1. Create a Template for Blog Posts

To render each blog post, we need a dedicated template that fetches and displays the content. Using Gatsby’s GraphQL layer, we can pull the necessary data from our Markdown files.

Here's a simple Gatsby page component for our blog posts:

import React from "react"
import { graphql, PageProps } from "gatsby"
import Layout from "../components/Layout"
import Section from "../components/Section"
import SEO from "../components/SEO"
import { BlogPostData } from "../types/blog.ts"

export default function BlogTemplate({ data }: { data: BlogPostData }) {
  const { markdownRemark } = data
  const { frontmatter, html } = markdownRemark

  return (
    <Layout>
      <Section>
        <div className="prose mx-auto">
          <h1>{frontmatter.title}</h1>
          <div dangerouslySetInnerHTML={{ __html: html }} />
        </div>
      </Section>
    </Layout>
  )
}

export const pageQuery = graphql`
  query ($slug: String!) {
    markdownRemark(frontmatter: { slug: { eq: $slug } }) {
      html
      frontmatter {
        categories
        date
        slug
        title
        short_description
      }
    }
    otherBlogPosts: allMarkdownRemark(
      filter: {
        fileAbsolutePath: { regex: "/blog/" }
        frontmatter: { slug: { ne: $slug } }
      }
      sort: { frontmatter: { date: DESC } }
    ) {
      edges {
        node {
          frontmatter {
            categories
            date
            slug
            title
            short_description
          }
        }
      }
    }
  }
`

The BlogTemplate function receives the data fetched by the GraphQL query and uses it to render the content of a blog post.

2. Use Markdown for Blog Posts

With Gatsby, content can be stored in Markdown format, which is both human-readable and easy to write. The "frontmatter" section at the top of each file provides metadata about the post (like title, categories, and date).

Here's an example of what the beginning of a Markdown blog post might look like:

---
title: "Example Blog Post" 
categories: ["Example", "Blog", "Post"]
date: 03.07.2023
short_description: "This is an example blog post."
slug: /blog/example-blog-post
---

# This is an Example Blog Post
Some text here.

The main content of the blog post follows the frontmatter, written in Markdown, which Gatsby will convert to HTML.

3. Integrate Blog Posts in gatsby-node.js

Gatsby uses the gatsby-node.js file to create pages programmatically. This means you can fetch your blog posts and create a page for each one.

Here's a snippet that does just that:

exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions

  const blogItemTemplate = require.resolve(
    `./src/templates/blogItemTemplate.tsx`
  )

  const blogResult = await graphql(`
    {
      allMarkdownRemark(
        filter: { fileAbsolutePath: { regex: "/blog/" } }
        sort: { frontmatter: { date: DESC } }
      ) {
        edges {
          node {
            frontmatter {
              categories
              date
              slug
              title
            }
            html
          }
        }
      }
    }
  `)

  // Handle errors
  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }

  blogResult.data.allMarkdownRemark.edges.forEach(({ node }) => {
    createPage({
      path: node.frontmatter.slug,
      component: blogItemTemplate,
      context: {
        // additional data can be passed via context
        slug: node.frontmatter.slug,
      },
    })
  })
}

This code fetches all the Markdown files in a "blog" directory, then creates a page for each one using the blog post template we defined earlier.

4. Create an Overview Page to Display All Blog Posts

It's common to have an overview or index page where visitors can see a list of all your blog posts. The code below achieves this by fetching all the blog posts and displaying their titles, with each title being a link to the full post:

import { graphql, Link } from "gatsby"
import React from "react"
import Layout from "../components/Layout"
import Section from "../components/Section"
import { H1 } from "../components/text/H1"
import { BlogPostData } from "../types/blog.ts"

const Blog = ({ data }: { data: BlogPostData }) => {
  const posts = data.allMarkdownRemark.edges.map(
    (edge: any) => edge.node.frontmatter
  )

  return (
    <Layout>
      <Section>
        <H1 text="Blog" />
        <div className="mt-10 space-y-16 border-t border-gray-200 pt-10 sm:mt-16 sm:pt-16">
          {posts.map((post: any) => (
            <article
              key={post.id}
              className="flex max-w-xl flex-col items-start justify-between"
            >
              <div className="flex items-center gap-x-4 text-xs">
                <time dateTime={post.datetime} className="text-gray-500">
                  {post.date}
                </time>
                {post.categories.map((category: string) => (
                  <button className="relative z-10 rounded-full bg-gray-50 px-3 py-1.5 font-medium text-gray-600 hover:bg-gray-100">
                    {category}
                  </button>
                ))}
              </div>
              <div className="group relative">
                <h2 className="mt-3 text-lg font-semibold leading-6 text-gray-900 group-hover:text-gray-600">
                  <Link to={post.slug}>
                    <span className="absolute inset-0" />
                    {post.title}
                  </Link>
                </h2>
                <p className="mt-5 line-clamp-3 text-sm leading-6 text-gray-600">
                  {post.description}
                </p>
              </div>
            </article>
          ))}
        </div>
      </Section>
    </Layout>
  )
}

export default Blog

export const Head = () => {
  return <SEO />
}

export const pageQuery = graphql`
    allMarkdownRemark(
      filter: { fileAbsolutePath: { regex: "/blog/" } }
      sort: { frontmatter: { date: DESC } }
    ) {
      edges {
        node {
          frontmatter {
            categories
            date
            slug
            title
            short_description
          }
        }
      }
    }
  }
`

This Blog component maps over all the posts and displays them in a styled manner using TailwindCSS classes. The page then gets exported and can be added to your site’s navigation.


Conclusion: With just these four steps, you've got the foundation of a Gatsby blog! Gatsby’s ecosystem and plugins, combined with the power of TailwindCSS and the simplicity of Markdown, make for a compelling blogging platform. Whether you're a seasoned developer or just starting out, this setup provides a solid, fast, and stylish base for sharing your thoughts with the world.