Back to Writing
Automation

Content Publishing Workflow: How This Writing Section Works

December 28, 2024
6 min read
#mdx#rss#automation#nextjs

This site's writing section is built on a simple but powerful content publishing system. No CMS, no database—just Markdown files that become fully-featured blog posts.

The Publishing Model

Every post you're reading is an MDX file stored in the repository. When you deploy, Next.js pre-renders every post as static HTML. Fast, simple, version-controlled.

Directory Structure

content/writing/
├── systems-design/
│   └── building-reliable-systems.mdx
├── monitoring/
│   └── your-post-here.mdx
├── automation/
│   └── content-publishing-workflow.mdx
└── research/
    └── another-post.mdx

Categories are folders. Drop a .mdx file in the right folder, and it appears on the site. No configuration needed.

Creating a New Post

Step 1: Choose Your Category

Four categories are available:

  • systems-design – Architecture, design patterns, system modeling
  • monitoring – Observability, alerting, operational excellence
  • automation – Workflows, tooling, efficiency
  • research – Investigations, experiments, deep dives

Create your file in the appropriate directory.

Step 2: Add Frontmatter

Every post needs YAML frontmatter at the top:

---
title: "Your Post Title Here"
excerpt: "A one-sentence summary that appears in lists and RSS feeds."
publishedAt: "2024-12-28"
category: "Automation"
tags: ["tag1", "tag2", "tag3"]
---

Field Rules:

  • title: Required. Used for page title and Open Graph metadata.
  • excerpt: Required. Keep it under 160 characters for SEO.
  • publishedAt: Required. ISO date format (YYYY-MM-DD).
  • category: Must match the folder name display format.
  • tags: Optional array. Used for related post recommendations.

Step 3: Write in MDX

MDX is Markdown with superpowers. Standard Markdown works, plus you get:

Code blocks with syntax highlighting:

interface Post {
  slug: string;
  title: string;
  publishedAt: string;
}

const posts = getAllPosts();

Custom components:

You can use the custom Callout component for emphasis:

<Callout type="info">
This is an informational callout. Type can be "info", "warning", or "success".
</Callout>

Smart links:

External links automatically open in new tabs. Internal links use Next.js routing.

How the RSS Feed Works

The RSS feed is generated dynamically at build time from your content files.

Feed Generation Process

  1. Content Parsing: The getAllPosts() function reads every .mdx file
  2. Metadata Extraction: Frontmatter is parsed with gray-matter
  3. RSS XML Construction: Posts are formatted as RSS 2.0 XML
  4. Static Route: Served at /writing/rss.xml

The Feed Route

Located at app/writing/rss.xml/route.ts:

export async function GET() {
  const posts = getAllPosts();
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://attakorah.com";

  const rss = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Attakorah - Writing</title>
    <link>${siteUrl}/writing</link>
    ...
  </channel>
</rss>`;

  return new Response(rss, {
    headers: {
      "Content-Type": "application/xml",
      "Cache-Control": "public, max-age=3600",
    },
  });
}

What Gets Included

Each post in the RSS feed includes:

  • Title: Your post title
  • Link: Direct URL to the post
  • GUID: Permanent unique identifier (same as link)
  • Description: Your excerpt text
  • Publication Date: RFC 2822 formatted date
  • Categories: Your category and all tags

Feed Updates

The RSS feed updates automatically when you:

  1. Add a new post
  2. Modify existing frontmatter
  3. Deploy the site

No manual regeneration needed. It's built at deploy time.

Sitemap Integration

Every post is automatically added to the XML sitemap at /sitemap.xml.

Sitemap Priority

  • Homepage: 1.0
  • Writing index: 0.9
  • Individual posts: 0.8

Change Frequency

Posts are marked as monthly changeFrequency, signaling to search engines that content is stable but may be updated.

The Content Parsing System

The heart of the system is lib/content.ts. It provides:

Core Functions

getAllPosts() Returns all posts sorted by publication date (newest first). Used by:

  • Writing list page
  • RSS feed
  • Sitemap generation

getPostBySlug(slug) Fetches a single post with full content. Used by individual post pages.

getRelatedPosts(slug, limit) Finds related posts using a scoring algorithm:

  • Same category: +10 points
  • Shared tag: +1 point per tag

Returns the top N highest-scoring posts.

Reading Time Calculation

Every post displays estimated reading time. Calculated automatically using the reading-time package:

import readingTime from "reading-time";

const stats = readingTime(content);
const minutes = Math.ceil(stats.minutes); // Always rounds up

Average reading speed: 200 words per minute (industry standard).

Deployment Workflow

Local Development

npm run dev

Posts are parsed on-demand during development. Hot reload works for content changes.

Production Build

npm run build

All posts are:

  1. Parsed and validated
  2. Converted to static HTML
  3. Added to sitemap
  4. Included in RSS feed

What Happens at Build

  1. getAllPosts() scans content directory
  2. Frontmatter is extracted and validated
  3. MDX content is compiled to React components
  4. Static pages are generated for each slug
  5. Sitemap and RSS feed are generated

Build Output

Route (app)
┌ ○ /writing
├ ● /writing/[slug]
│ └ /writing/building-reliable-systems
│ └ /writing/content-publishing-workflow
└ ƒ /writing/rss.xml
  • = Static page
  • = SSG (Static Site Generation)
  • ƒ = Dynamic route

Performance Characteristics

Why This Approach Works

No Database Queries: Content is read from filesystem at build time, not runtime.

No Client-Side Rendering: Posts are pre-rendered HTML. First paint is instant.

CDN-Friendly: Every page is a static asset. Cache forever, invalidate on deploy.

Version Control: Content lives in Git. Full history, branching, pull requests.

Trade-offs

Not Suitable For:

  • User-generated content
  • Real-time updates
  • Content that changes hourly

Perfect For:

  • Technical writing
  • Documentation
  • Long-form articles
  • Portfolios

Adding Your First Post

Ready to publish? Here's the complete workflow:

1. Create the File

touch content/writing/systems-design/my-first-post.mdx

2. Add Frontmatter and Content

---
title: "My First Post"
excerpt: "Learning how to publish content on this platform."
publishedAt: "2024-12-28"
category: "Systems Design"
tags: ["tutorial", "getting-started"]
---

# My First Post

Your content here...

3. Preview Locally

npm run dev
# Visit http://localhost:3000/writing/my-first-post

4. Build and Deploy

npm run build
# Commit and push to trigger deployment

5. Verify

Check these URLs:

  • Post page: /writing/my-first-post
  • Writing index: /writing (should show your post)
  • Sitemap: /sitemap.xml (should include your post)
  • RSS: /writing/rss.xml (should list your post)

Conclusion

This publishing system prioritizes simplicity and performance. No admin panel, no database, no complexity.

Write Markdown. Commit. Deploy. Done.

The automation handles everything else: parsing, rendering, sitemap updates, RSS feed generation, and static site optimization.

It's reliable because it's simple. It's fast because it's static. It works because it's built on fundamentals that don't change.