A simple static site generator for Markdown content, built with Scala 3.
Blarg transforms your Markdown files into a complete static website with blog support, tags, templating, and built-in link validation. Perfect for personal sites, blogs, documentation, and project pages.
- Markdown-First: Write content in Markdown with extensive CommonMark extension support
- Blog-Ready: Built-in blog post management with date-based URLs and latest posts page
- Smart Organization: Automatic tag pages and chronological post ordering
- Template System: Mustache templates with partials for flexible layouts
- Link Validation: Catches broken internal links before deployment
- Live Development: Watch mode rebuilds on file changes
- Built-in Server: Serve your site locally for testing
Blarg supports these CommonMark extensions out of the box:
- Autolink - Automatic URL linking
- Strikethrough -
~~strikethrough~~text - Tables - GitHub-flavored tables
- Footnotes - Reference-style footnotes
- Heading Anchors - Automatic ID generation for headings
- Ins -
++inserted++text - YAML Frontmatter - Metadata for pages and posts
- Image Attributes - Width, height, and other image properties
You can check the GitHub releases page for pre-built linux-x64 versions (should also work on Mac). If you want to build from source, read on.
- Java 21 or later (defaults to Java 23)
- Scala CLI (for building from source)
git clone https://github.com/alterationx10/blarg.git
cd blarg
scala-cli --power package . -o blarg -fThis creates a standalone executable blarg that you can move to your PATH.
blarg new my-blog
cd my-blogThis creates the following structure:
my-blog/
├── site/
│ ├── blarg.json # Site configuration
│ ├── blog/ # Blog posts
│ │ └── YYYY-MM-DD-first-post.md
│ ├── pages/ # Static pages
│ │ ├── index.md
│ │ └── about.md
│ ├── static/ # CSS, JS, images
│ │ ├── css/
│ │ ├── js/
│ │ └── img/
│ └── templates/ # Mustache templates
│ ├── site.mustache
│ ├── pages/
│ │ ├── page.mustache
│ │ ├── blog.mustache
│ │ ├── tags.mustache
│ │ └── latest.mustache
│ └── partials/
│ ├── header.mustache
│ ├── nav.mustache
│ └── footer.mustache
└── .gitignore
blarg build -d ./siteThe generated site appears in ./build/ (sibling to the site folder).
blarg serveVisit http://localhost:9000 to preview your site.
blarg build -d ./site --watchBlarg will rebuild automatically when files change. Press Return to stop.
Create a new site with the default template structure.
Options:
-d, --dir <path>- Parent directory for the new site (default:.)
Examples:
blarg new my-blog
blarg new my-blog -d ~/projectsBuild the static site from Markdown files.
Options:
-d, --dir <path>- Path to site folder (default:./site)-w, --watch- Watch for changes and rebuild automatically
Examples:
blarg build
blarg build -d ./site
blarg build -d ./site --watchOutput:
- Generated files go to
../build/relative to the site folder - Blog posts:
/YYYY/MM/DD/slug.html - Pages:
/page-name.html - Special pages:
/tags.html,/latest.html
Start a local HTTP server to preview your site.
Options:
-d, --dir <path>- Directory to serve (default:./build)-p, --port <number>- Port number (default:9000)--no-tty- Don't wait for user input (useful in scripts)
Examples:
blarg serve
blarg serve -p 8080
blarg serve -d ./build --no-ttyGenerate new pages or blog posts with frontmatter templates.
Options:
-b, --blog <title>- Create a new blog post with the given title-p, --page <path>- Create a new page at the given path-fm, --frontmatter <path>- Add frontmatter to an existing file-d, --dir <path>- Site root directory (default:./site)
Examples:
# Create a blog post
blarg gen -b "My First Post"
# Creates: site/blog/YYYY-MM-DD-my-first-post.md
# Create a page
blarg gen -p about.md
# Creates: site/pages/about.md
# Create a nested page
blarg gen -p docs/getting-started.md
# Creates: site/pages/docs/getting-started.md
# Add frontmatter to existing file
blarg gen -fm content/old-post.mdThe blarg.json file in your site folder controls global site settings.
Example:
{
"siteTitle": "My Awesome Blog",
"author": "Your Name",
"navigation": [
{
"label": "Home",
"href": "/"
},
{
"label": "Blog",
"href": "/latest.html"
},
{
"label": "Tags",
"href": "/tags.html"
},
{
"label": "About",
"href": "/about.html"
}
],
"dynamic": {
"socialLinks": {
"github": "https://github.com/yourusername"
},
"analytics": {
"enabled": true,
"trackingId": "UA-XXXXX-Y"
}
}
}Fields:
siteTitle- Your site's name (used in templates)author- Default author namenavigation- Array of navigation linkslabel- Link texthref- Link URL
dynamic- Free-form JSON object for custom template variables
Accessing in Templates:
<title>{{config.siteTitle}}</title>
<p>By {{config.author}}</p>
{{#config.navigation}}
<a href="{{href}}">{{label}}</a>
{{/config.navigation}}
{{#config.dynamic.socialLinks.github}}
<a href="{{config.dynamic.socialLinks.github}}">GitHub</a>
{{/config.dynamic.socialLinks.github}}Frontmatter is YAML metadata at the top of Markdown files, enclosed by ---.
Example:
---
title: My Blog Post
description: A brief description of the post
author: Jane Doe
published: 2025-01-15T10:00:00Z
lastUpdated: 2025-01-16T14:30:00Z
tags:
- tutorial
- markdown
- web-development
---
# My Blog Post
Your content here...Fields:
| Field | Type | Required | Description |
|---|---|---|---|
title |
String | No | Page or post title |
description |
String | No | Brief description/summary |
author |
String | No | Author name (overrides site default) |
published |
ISO 8601 Date | No | Publication date |
lastUpdated |
ISO 8601 Date | No | Last modification date |
tags |
Array[String] | No | List of tags for categorization |
Notes:
- All fields are optional
- Dates must be in ISO 8601 format:
YYYY-MM-DDTHH:MM:SSZ - Tags are used to generate the tags page automatically
- Leave frontmatter empty with just
---if no metadata needed
Blarg uses Mustache for templating, with a hierarchical structure:
-
Site Template (
templates/site.mustache)- Master layout wrapper
- Contains
{{{content}}}placeholder for page content
-
Content Templates (
templates/pages/*.mustache)- Defines content structure
- Wrapped by site template
- Available templates:
page.mustache- Static pagesblog.mustache- Individual blog poststags.mustache- Tag listing pagelatest.mustache- Recent posts page
-
Partials (
templates/partials/*.mustache)- Reusable components
- Inserted with
{{> partialName}} - Default partials:
header.mustachenav.mustachefooter.mustache
Global Context (available in all templates):
{{buildTime}} <!-- Build timestamp (ISO 8601) -->
{{year}} <!-- Current year (for copyright) -->
<!-- Configuration -->
{{config.siteTitle}}
{{config.author}}
{{#config.navigation}}
{{label}}
{{href}}
{{/config.navigation}}
{{config.dynamic.*}} <!-- Any custom fields from blarg.json -->
<!-- Content (varies by page type) -->
{{{content}}}Page/Blog Content Context:
<!-- HTML content -->
{{{content.content}}}
<!-- Frontmatter -->
{{content.fm.title}}
{{content.fm.description}}
{{content.fm.author}}
{{content.fm.published}} <!-- Formatted: "January 15, 2025" -->
{{content.fm.lastUpdated}} <!-- Formatted: "January 16, 2025" -->
{{#content.fm.tags}}
{{.}} <!-- Individual tag -->
{{/content.fm.tags}}
<!-- Metadata -->
{{content.href}} <!-- Page URL -->
{{content.summary}} <!-- First paragraph (for listings) -->Tags Page Context:
{{#content}}
{{tag}} <!-- Tag name -->
{{#articles}}
{{fm.title}}
{{href}}
{{summary}}
{{/articles}}
{{/content}}Latest Posts Page Context:
{{#content}}
{{fm.title}}
{{fm.published}}
{{href}}
{{summary}}
{{/content}}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{content.fm.title}} - {{config.siteTitle}}</title>
<meta name="description" content="{{content.fm.description}}">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
{{> header}}
{{> nav}}
<main>
{{{content}}}
</main>
{{> footer}}
<script src="/js/main.js"></script>
</body>
</html><article>
<header>
<h1>{{content.fm.title}}</h1>
<p class="meta">
By {{content.fm.author}} on {{content.fm.published}}
{{#content.fm.lastUpdated}}
(Updated: {{content.fm.lastUpdated}})
{{/content.fm.lastUpdated}}
</p>
{{#content.fm.tags}}
<span class="tag">{{.}}</span>
{{/content.fm.tags}}
</header>
<div class="content">
{{{content.content}}}
</div>
</article>Blog posts must follow the date-based naming convention:
YYYY-MM-DD-slug.md
Examples:
2025-01-15-my-first-post.md2025-12-25-holiday-special.md
Blog posts are organized by date in the generated site:
site/blog/2025-01-15-my-first-post.md
→ build/2025/01/15/my-first-post.html
You can organize blog posts in subdirectories:
site/blog/tutorials/2025-01-15-getting-started.md
→ build/tutorials/2025-01-15-getting-started.html
Static pages in the site/pages/ directory become root-level pages:
site/pages/about.md → build/about.html
site/pages/index.md → build/index.html
Create subdirectories for organized page structure:
site/pages/docs/guide.md → build/docs/guide.html
site/pages/projects/web-app.md → build/projects/web-app.html
Files in site/static/ are copied to the build folder as-is:
site/static/css/style.css → build/css/style.css
site/static/js/main.js → build/js/main.js
site/static/img/logo.png → build/img/logo.png
Reference static files in templates and Markdown using root-relative paths:
<link rel="stylesheet" href="/css/style.css">
<img src="/img/logo.png" alt="Logo">Blarg automatically validates internal links during builds and reports broken links:
WARNING: Found 2 broken internal link(s):
In /index.html:
Link to '/missing-page' (tried: /missing-page.html) - page not found
In /blog/2025/01/15/post.html:
Link to '/about' (tried: /about.html) - page not found
Link Resolution:
/pagechecks for/page.html,/page/index.html/page.htmlchecks directly/page/checks for/page/index.html- Static files are also validated (CSS, JS, images)
- External links (
http://,https://,mailto:) are not validated - Fragments (
#anchor) and query strings (?param=value) are ignored - Relative links (like
../other.html) are not validated
"ERROR: Config file not found"
- Make sure
blarg.jsonexists in your site folder - Check that you're pointing to the correct directory with
-d
"ERROR: Failed to parse config file"
- Validate your JSON syntax at https://jsonlint.com
- Ensure all required fields are present:
siteTitle,author,navigation
"ERROR: Template not found"
- Verify all required templates exist in
site/templates/:site.mustachepages/page.mustachepages/blog.mustachepages/tags.mustachepages/latest.mustachepartials/header.mustachepartials/nav.mustachepartials/footer.mustache
"WARNING: Found broken internal link(s)"
- Review the reported broken links
- Fix the href attributes in your Markdown or templates
- Links should be absolute paths starting with
/
"WARNING: Failed to copy static files"
- Check file permissions in the
site/static/folder - Verify the build directory is writable
Changes not triggering rebuild
- Make sure the file is inside the
site/folder - Some text editors use atomic saves which may not trigger watch events
- Try manually saving again or restart watch mode
Build fails after creating new directory
- This should now work automatically (fixed in latest version)
- If issues persist, restart watch mode
Port already in use
- Another process is using the port (default: 9000)
- Use
-pflag to specify a different port:blarg serve -p 8080 - Find and stop the other process using the port
404 errors when serving
- Make sure you've built the site first:
blarg build - Verify the correct directory with
-d ./build - Check that the requested file exists in the build folder
# 1. Generate a new post
blarg gen -b "How to Use Blarg"
# 2. Edit the generated file
vim site/blog/YYYY-MM-DD-how-to-use-blarg.md
# 3. Build in watch mode for live preview
blarg build --watch &
# 4. Serve the site
blarg serve
# 5. Preview at http://localhost:9000
# Make changes and see them rebuild automatically
# 6. Stop watch mode (press Return)
# Deploy the build/ folder to your hosting# 1. Create the site
blarg new my-docs
# 2. Generate documentation pages
cd my-docs
blarg gen -p getting-started.md
blarg gen -p api-reference.md
blarg gen -p tutorials/basic.md
blarg gen -p tutorials/advanced.md
# 3. Build and serve
blarg build && blarg serve
# 4. Deploy
rsync -av build/ user@server:/var/www/docs/Blarg generates a static site in the build/ folder. Deploy it to any static hosting service.
- Use descriptive filenames for pages
- Organize related content in subdirectories
- Use consistent tag names across posts (case-sensitive!)
- Keep static assets organized by type (css/, js/, img/)
- Optimize images before adding to
static/img/ - Minimize CSS and JavaScript for production
- Use appropriate image formats (WebP, optimized PNG/JPEG)
- Write clear, descriptive frontmatter titles
- Add descriptions for better SEO
- Use tags consistently for better navigation
- Keep the first paragraph concise (used as summary)
- Keep templates modular with partials
- Use descriptive variable names in
dynamicconfig - Test template changes locally before deploying
- Commit the
site/folder (source content) - make sure
build/is added to.gitignore - Keep templates and config in version control
- Document custom template variables in your README
Contributions are welcome! Please feel free to submit issues and pull requests.
[Your license here]
Built with:
- Scala 3
- CommonMark Java - Markdown parsing
- Branch framework - CLI, templating, and HTTP server
Happy blogging with Blarg! 🎉