A lightweight blog where posts are written in Markdown files and rendered beautifully in the browser — no server, no CMS, no database needed.
We write posts as .md files, load them with fetch(), parse Markdown to HTML using a tiny parser, and render them. The whole blog runs from a single index.html.
// File structure:
// index.html ← the app shell
// style.css ← styling
// posts/
// hello-world.md ← your first post
// my-second-post.md
Create posts/hello-world.md — standard Markdown syntax works out of the box.
---
title: Hello World
date: 2026-08-09
author: Your Name
---
# Hello, World!
Welcome to my blog. This post is written in **Markdown** and rendered live in the browser.
## Why Markdown?
- Clean and readable as plain text
- Converts to HTML automatically
- Portable — works with any static site generator
## Code example
```python
def greet(name):
return f"Hello, {name}!"
print(greet("World"))
```
> *"The best way to predict the future is to build it."*
Create index.html with a simple Markdown-to-HTML parser using regex replacements.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Blog</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<?php require_once __DIR__ . '/../promo_banner.php'; ?>
<header>
<a href="#" onclick="showList()" class="logo">My Blog</a>
</header>
<main id="app"></main>
<script>
// Simple Markdown → HTML parser
function parseMarkdown(md) {
// Remove frontmatter (--- ... ---)
md = md.replace(/^---[\s\S]*?---\n/, '');
return md
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.+?)\*/g, '<em>$1</em>')
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/```[\w]*\n([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')
.replace(/^\- (.+)$/gm, '<li>$1</li>')
.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>')
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
.replace(/\n\n/g, '</p><p>')
.replace(/^(?!<[hupbac])/gm, '<p>')
.replace(/(?<![>])$/gm, '</p>');
}
// Extract frontmatter value
function getMeta(md, key) {
const match = md.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
return match ? match[1].trim() : '';
}
</script>
<script src="app.js"></script>
</body>
</html>
Define your post list in app.js, load them with fetch(), and render them.
// app.js
// List all your posts here
const POSTS = [
{ slug: 'hello-world', title: 'Hello World', date: '2026-08-09' },
{ slug: 'my-second-post', title: 'My Second Post', date: '2026-08-10' },
];
function showList() {
const app = document.getElementById('app');
app.innerHTML = `
<div class="post-list">
<h1>All Posts</h1>
${POSTS.map(p => `
<div class="post-card" onclick="showPost('${p.slug}')">
<div class="post-date">${p.date}</div>
<h2>${p.title}</h2>
<span class="read-link">Read →</span>
</div>
`).join('')}
</div>`;
}
async function showPost(slug) {
const res = await fetch(`posts/${slug}.md`);
const text = await res.text();
const html = parseMarkdown(text);
const title = getMeta(text, 'title');
const date = getMeta(text, 'date');
document.getElementById('app').innerHTML = `
<article class="post">
<div class="post-meta">
<span class="back" onclick="showList()">← All Posts</span>
<span>${date}</span>
</div>
<div class="post-content">${html}</div>
</article>`;
}
// Start on the post list
showList();
[Blog homepage shows:] All Posts ───────────────────────── 2026-08-09 Hello World Read → 2026-08-10 My Second Post Read → [Click a post — beautifully rendered Markdown with headings, code blocks, bold text]
Your blog has no backend, no database, no hosting costs. Add a new post by dropping a .md file in /posts/ and adding one line to the POSTS array in app.js. For syntax highlighting in code blocks, add highlight.js with one script tag.
Spotted a bug, broken code, or something that doesn't look right? Tell us what's off and we'll fix it.