How Markdown to HTML and SSG Works
How Markdown to HTML and SSG Works
Building a website shouldn't require complex databases or server-side processing. SSG Portfolio demonstrates how simple, powerful tools can transform plain-text Markdown files into a complete static website. Let's explore how this magic happens.
What is Markdown?
Markdown is a lightweight, human-readable markup language created in 2004. Instead of writing complex HTML tags, you write simple, readable text:
# This is a heading
This is a paragraph with **bold** and *italic* text.
- Bullet point 1
- Bullet point 2
[Link text](https://example.com)
When processed, this becomes clean, semantic HTML. Markdown is perfect for content because it's:
- Easy to write - Focus on content, not formatting
- Version control friendly - Plain text works great with Git
- Portable - Works anywhere, no proprietary formats
- Readable - Even unprocessed, it's easy to read
The SSG Portfolio Workflow
Step 1: File Organization
All content lives in the /content/ directory as Markdown files (.md). Each file represents one post or page. The filename becomes the URL slug:
content/
├── my-article.md → /my-article.html
├── getting-started.md → /getting-started.html
└── images/ → /images/ (copied to dist)
Step 2: YAML Frontmatter
At the top of each Markdown file, we add metadata in YAML format:
---
title: "Article Title"
date: 2024-01-28
tags: ["tag1", "tag2"]
description: "Brief summary for search engines"
slug: "custom-url-slug" # Optional
---
# Article content starts here...
Why frontmatter? It separates content metadata (title, date, tags) from the body. The generator uses this to:
- Create page titles
- Sort posts by date
- Organize by tags
- Generate meta tags for SEO
Step 3: Markdown Parsing
The build process uses three key Node.js packages:
1. gray-matter - Extracts frontmatter from Markdown files
const matter = require('gray-matter');
const file = matter.read('my-article.md');
console.log(file.data); // { title, date, tags, ... }
console.log(file.content); // Raw markdown body
2. marked - Converts Markdown to HTML
const { marked } = require('marked');
const html = marked.parse('# Heading\n\nParagraph');
// Output: <h1>Heading</h1><p>Paragraph</p>
3. handlebars - Injects content into templates
const handlebars = require('handlebars');
const template = handlebars.compile(layoutHTML);
const finalHTML = template({ title, content, tags, date });
Step 4: Template System
SSG Portfolio uses Handlebars templates to define the visual structure. Templates have two types:
Layouts (/templates/layouts/) - Page structures:
post.hbs- Single post layout with header, content, footerindex.hbs- Homepage listing all postspage.hbs- Generic page template
Partials (/templates/partials/) - Reusable components:
header.hbs- Site headernavigation.hbs- Navigation menufooter.hbs- Site footerpost-card.hbs- Post card for listingspost-meta.hbs- Date and tags display
How Templates Work
A layout file looks like:
<!DOCTYPE html>
<html>
<head>
<title>{{title}} | {{site.title}}</title>
</head>
<body>
{{> header}}
{{> navigation}}
<main>
<h1>{{title}}</h1>
{{{content}}}
</main>
{{> footer}}
</body>
</html>
Template variables available:
{{title}}- Post title{{content}}- Rendered HTML content{{date}}- Publication date{{tags}}- Array of tags{{site.title}}- Site configuration{{> partial-name}}- Include a partial
Notice {{{content}}} with three braces - that tells Handlebars NOT to escape HTML, so the Markdown-to-HTML conversion is preserved.
Step 5: The Build Process
When you run npm run build, the generator orchestrates everything:
┌─────────────────┐
│ 1. Load Config │ (site.config.js)
└────────┬────────┘
│
┌────────▼──────────────┐
│ 2. Parse Markdown │ (gray-matter + marked)
│ - Extract frontmatter│
│ - Convert to HTML │
│ - Sort by date │
└────────┬──────────────┘
│
┌────────▼────────────────┐
│ 3. Register Partials │ (Load all .hbs files)
└────────┬────────────────┘
│
┌────────▼──────────────────────────┐
│ 4. Generate Individual Posts │
│ - Use post.hbs layout │
│ - Inject each post's content │
│ - Write to /dist/*.html │
└────────┬──────────────────────────┘
│
┌────────▼──────────────────────────┐
│ 5. Generate Index/Homepage │
│ - Use index.hbs layout │
│ - Loop through all posts │
│ - Display as post cards │
│ - Write index.html │
└────────┬──────────────────────────┘
│
┌────────▼──────────────────┐
│ 6. Copy Static Assets │
│ - Images (/content/images)
│ - CSS (/static/style.css)
│ - Other files │
└────────┬──────────────────┘
│
┌────────▼────────────────────┐
│ 7. Output Complete │
│ /dist/ ready for deployment │
└──────────────────────────────┘
Step 6: Output Structure
After the build, /dist/ contains:
dist/
├── index.html # Homepage (lists all posts)
├── my-article.html # Individual post pages
├── getting-started.html
├── style.css # Stylesheet
├── images/ # Copied from /content/images/
│ ├── example.jpg
│ └── subfolder/
│ └── nested.jpg
└── (any other static files)
Why This Architecture?
Advantages
Speed 🚀
- No database queries
- No server processing
- Instant page loads
- Perfect for CDN caching
Security 🔒
- No server vulnerabilities
- No SQL injection risks
- No authentication needed
- Just static files
Version Control 📝
- All content in Git
- Track changes with commits
- Collaborate with branches
- Full history preserved
Simplicity ✨
- No complex setup
- No dependencies to manage
- Easy to understand
- Easy to customize
Portability 🌍
- Deploy anywhere (Netlify, Cloudflare Pages, GitHub Pages, etc.)
- No vendor lock-in
- Works with any hosting
- Can work offline
The Complete Flow: Example
Let's trace what happens to this file:
1. File: /content/my-article.md
```markdown
title: "My Article" date: 2024-01-28 tags: ["markdown", "tutorial"] description: "Learn about Markdown"
My Article
This is bold text.
- Point 1
- Point 2
**2. gray-matter extracts:**
```javascript
{
data: {
title: "My Article",
date: "2024-01-28",
tags: ["markdown", "tutorial"],
description: "Learn about Markdown"
},
content: "# My Article\n\nThis is **bold** text..."
}
3. marked converts content to:
<h1>My Article</h1>
<p>This is <strong>bold</strong> text.</p>
<ul>
<li>Point 1</li>
<li>Point 2</li>
</ul>
4. Handlebars renders with post.hbs:
<!DOCTYPE html>
<html>
<head>
<title>My Article | SSG Portfolio</title>
</head>
<body>
<!-- header and nav partials -->
<main>
<article>
<h1>My Article</h1>
<p>📅 2024-01-28</p>
<div class="tags">
<span>markdown</span>
<span>tutorial</span>
</div>
<h1>My Article</h1>
<p>This is <strong>bold</strong> text.</p>
<ul>
<li>Point 1</li>
<li>Point 2</li>
</ul>
</article>
</main>
<!-- footer partial -->
</body>
</html>
5. Output: /dist/my-article.html ✅
Browser receives clean, semantic HTML - no JavaScript processing needed!
Key Takeaways
- Markdown is the content format - simple, readable, version-control friendly
- YAML Frontmatter stores metadata - title, date, tags, description
- gray-matter separates frontmatter from content
- marked converts Markdown to HTML
- Handlebars injects content into template layouts
- Static output means fast, secure, portable websites
Next Steps
Now that you understand the process, you can:
- Write new posts by creating
.mdfiles in/content/ - Customize templates in
/templates/ - Modify styling in
/static/style.css - Deploy to any static hosting platform
The beauty of SSG is that once generated, the HTML is pure, fast, and lightweight. No frameworks, no runtime dependencies - just clean code and great content.
Want to learn more? Check out the README for writing post guides, or the DESIGN.md document for customizing templates and styles.