Skip to main content

Overview

The MilesONerd blog system is a serverless, client-side blog that fetches Markdown files directly from GitHub and renders them dynamically using Showdown.js. This approach eliminates the need for a backend server while maintaining full version control through Git.
The blog uses the GitHub API to fetch Markdown files from the repository, converting them to HTML on-the-fly for display.

Key Features

Serverless Architecture

No backend required - all content is served directly from GitHub

Markdown Support

Full Markdown syntax with Showdown.js conversion

Git Version Control

Blog posts are managed as Markdown files in the repository

Dynamic Loading

Posts load asynchronously without page refreshes

How It Works

The blog system consists of two main functions that work together to fetch and display content:
1

Fetch Post List

The fetchPostList() function queries the GitHub API to retrieve all Markdown files from the /old/blog/posts directory.
2

Display Post Links

Post filenames are converted to clickable links and displayed in the sidebar.
3

Load Post Content

When a user clicks a post link, loadPostContent() fetches the raw Markdown and converts it to HTML.
4

Render HTML

The converted HTML is injected into the content div for display.

Implementation

Configuration

The blog system is configured to point to the GitHub repository:
const converter = new showdown.Converter();

const username = 'MilesONerd';
const repo = 'milesonerd.github.io';
const folder = 'posts';
const branch = 'update-urls';

const postList = document.getElementById('post-list');
const contentDiv = document.getElementById('content');
The showdown.Converter() instance is created globally to be reused for all Markdown conversions.

Fetching the Post List

The fetchPostList() function retrieves all blog posts from GitHub:
async function fetchPostList() {
    const url = `https://api.github.com/repos/${username}/${repo}/contents/old/blog/${folder}?ref=${branch}`;

    try {
        const response = await fetch(url);
        if (!response.ok) throw new Error('Error loading post list.');

        const files = await response.json();
        const mdFiles = files.filter(file => file.name.endsWith('.md'));

        postList.innerHTML = '';

        mdFiles.forEach(file => {
            const listItem = document.createElement('li');
            listItem.innerHTML = `<a href="#" data-url="${file.download_url}">${file.name.replace('.md', '')}</a>`;
            postList.appendChild(listItem);
        });

        postList.addEventListener('click', (e) => {
            if (e.target.tagName.toLowerCase() === 'a') {
                e.preventDefault();
                const postUrl = e.target.getAttribute('data-url');
                loadPostContent(postUrl);
            }
        });

    } catch (error) {
        console.error(error);
        postList.innerHTML = '<li>Error loading posts 😢</li>';
    }
}
The function constructs a URL to the GitHub API’s /contents endpoint, which returns metadata about all files in the specified directory. The response includes:
  • File names
  • Download URLs for raw content
  • File sizes and types
Only files ending in .md are filtered and displayed as blog posts.

Loading Post Content

When a user clicks a post, the loadPostContent() function retrieves and renders it:
async function loadPostContent(url) {
    try {
        const response = await fetch(url);
        if (!response.ok) throw new Error('Error loading post.');

        const markdown = await response.text();
        const htmlContent = converter.makeHtml(markdown);
        contentDiv.innerHTML = htmlContent;
    } catch (error) {
        console.error(error);
        contentDiv.innerHTML = '<p>Error loading post content 😢</p>';
    }
}
Showdown.js’s makeHtml() method converts Markdown to HTML in a single call, supporting all standard Markdown syntax including headers, lists, code blocks, and links.

Initialization

The blog automatically loads the post list when the script runs:
fetchPostList();

Blog Post Structure

Blog posts are stored as Markdown files in the /old/blog/posts/ directory. Here’s an example structure:
# My First Blog Post

## Introduction

This is an example blog post written in Markdown.

## Features

- Support for **bold** and *italic* text
- Code blocks with syntax highlighting
- Lists and quotes
- Links and images

## Code Example

\`\`\`javascript
const greeting = "Hello, World!";
console.log(greeting);
\`\`\`

## Conclusion

Markdown makes writing blog posts simple and enjoyable!

Advantages

Zero Server Costs

Hosting is free via GitHub Pages with no backend infrastructure

Version Controlled

Every blog post edit is tracked in Git history

Easy Content Management

Write posts in any text editor using simple Markdown syntax

Fast Performance

Client-side rendering keeps the blog lightweight and responsive

Error Handling

The system includes comprehensive error handling for both API failures and content loading issues:
If the GitHub API is rate-limited or unavailable, users will see a friendly error message: “Error loading posts 😢“
postList.innerHTML = '<li>Error loading posts 😢</li>';

Dependencies

The blog system requires two external libraries:
  1. Showdown.js - Markdown to HTML converter
  2. GitHub API - Content delivery and file listing
<!-- Include Showdown.js -->
<script src="https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.min.js"></script>

<!-- Blog system script -->
<script src="/old/blog/scripts/list-posts.js"></script>

File Location

The complete blog system implementation can be found at: ~/workspace/source/old/blog/scripts/list-posts.js
The blog was part of the original milesonerd.github.io site and has been preserved in the /old/ directory of the current site.

Build docs developers (and LLMs) love