A fast, modern static site generator built with Elixir. Perfect for blogs, documentation sites, and marketing pages.
Features
- Markdown Content - Write your content in Markdown with YAML frontmatter
- EEx Templates - Powerful templating with Elixir's built-in EEx
- Collections - Automatic tag and category pages
- Asset Pipeline - Automatic minification and fingerprinting for CSS/JS
- Incremental Builds - Only rebuild changed files for lightning-fast builds
- Clean Architecture - Well-tested, maintainable codebase following SOLID principles
Quick Start
Installation
Add alkali to your mix.exs dependencies:
def deps do
[
{:alkali, "~> 0.1.0"}
]
endThen run:
mix deps.get
Tutorial: Creating Your First Site
Here's a complete walkthrough of creating, building, and adding content to a site:
1. Create a New Site
# Create a new site called "my_blog"
mix alkali.new my_blog
# Change into the site directory
cd my_blog
This creates a new site with the following structure:
my_blog/
├── config/
│ └── alkali.exs # Site configuration
├── content/
│ ├── posts/ # Your blog posts
│ │ └── 2024-01-15-hello-world.md
│ └── pages/ # Static pages
│ └── about.md
├── layouts/
│ ├── default.html.heex # Default layout
│ ├── post.html.heex # Post layout
│ ├── page.html.heex # Page layout
│ └── partials/ # Reusable partials
│ ├── nav.html
│ └── footer.html
├── static/
│ ├── css/
│ │ └── app.css
│ └── js/
│ └── app.js
└── _site/ # Generated output (created on build)2. Configure Your Site
Edit config/alkali.exs to customize your site:
import Config
config :alkali,
site: %{
title: "My Awesome Blog",
url: "https://myblog.com",
author: "Your Name",
description: "A blog about Elixir and web development"
}3. Build Your Site
# Build the site (generates files in _site/)
mix alkali.build
# Build with verbose output to see what's happening
mix alkali.build --verbose
# Build including draft posts
mix alkali.build --draft
# Clean and rebuild everything
mix alkali.build --clean
Your static site is now generated in the _site/ directory!
4. View Your Site Locally
# Serve the _site/ directory with any static server
# For example, using Python:
cd _site && python3 -m http.server 8000
# Or using Elixir:
cd _site && mix run -e 'Mix.Tasks.Run.run([])' -- --no-halt
Visit http://localhost:8000 to see your site!
5. Development Workflow
# Make changes to content or layouts
vim content/posts/2024-01-15-hello-world.md
# Rebuild (only changed files are rebuilt)
mix alkali.build
# Clear cache and force full rebuild if needed
rm .alkali_cache.json
mix alkali.build
Usage
Creating Content
Create a new post in content/posts/:
mix alkali.post "My First Post"
This creates a file like content/posts/2024-01-15-my-first-post.md:
---
title: My First Post
date: 2024-01-15
tags: [elixir, blog]
category: technology
layout: post
draft: false
---
# My First Post
This is my first post! You can write **Markdown** here.
- Lists work
- So do [links](https://example.com)
- And code blocks
\`\`\`elixir
defmodule Hello do
def world, do: "Hello, World!"
end
\`\`\`Frontmatter Fields
| Field | Required | Description |
|---|---|---|
title | Yes | Post title |
date | Yes | Publication date (ISO 8601 format) |
layout | No | Layout template to use (default: "default") |
tags | No | List of tags for categorization |
category | No | Single category |
draft | No | If true, post is hidden (default: false) |
Layouts
Layouts are EEx templates in the layouts/ directory:
<!DOCTYPE html>
<html>
<head>
<title><%= @page.title %> - <%= @site[:site_name] %></title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<header>
<h1><%= @site[:site_name] %></h1>
</header>
<main>
<%= @content %>
</main>
<footer>
<p>© 2024 <%= @site[:site_name] %></p>
</footer>
</body>
</html>Available Variables in Layouts
@page- Current page data (title, date, tags, etc.)@site- Site configuration fromconfig/alkali.exs@content- Rendered HTML content@collections- All collections (tags, categories)
Collections
Collections are automatically generated for tags and categories.
If you have posts with these tags:
tags: [elixir, phoenix, web]The following pages are automatically created:
/tags/elixir.html- All posts tagged "elixir"/tags/phoenix.html- All posts tagged "phoenix"/tags/web.html- All posts tagged "web"
Same for categories:
category: technologyCreates:
/categories/technology.html- All posts in "technology" category
Assets
Place your CSS, JavaScript, and images in the static/ directory:
static/
├── css/
│ └── app.css
├── js/
│ └── app.js
└── images/
└── logo.pngCSS & JavaScript
CSS and JS files are automatically:
- Minified - Comments removed, whitespace collapsed
- Fingerprinted - Cache-busting hashes added
- Referenced - HTML updated with fingerprinted URLs
Input: static/css/app.css
/* Comment */
body {
margin: 0;
padding: 0;
}Output: _site/css/app-3e67b4a9.css
body {
margin: 0;
padding: 0;
}HTML automatically updated:
<!-- Before -->
<link rel="stylesheet" href="/css/app.css" />
<!-- After -->
<link rel="stylesheet" href="/css/app-3e67b4a9.css" />Images & Binary Files
Images and other binary files are copied as-is without modification:
Input: static/images/logo.png
Output: _site/images/logo.png
Build Options
# Standard build
mix alkali.build
# Include draft posts
mix alkali.build --draft
# Clean output directory before building
mix alkali.build --clean
# Verbose output (show all steps)
mix alkali.build --verbose
Incremental Builds
By default, Alkali only rebuilds changed files:
# First build - builds everything
mix alkali.build
# => Built 10 files
# Edit one post
vim content/posts/my-post.md
# Second build - only rebuilds changed file
mix alkali.build
# => Rebuilt 1 file (1 changed, 9 skipped)
The build cache is stored in .alkali_cache.json. Delete this file to force a full rebuild:
rm .alkali_cache.json
mix alkali.build
Mix Tasks
mix alkali.new <name>
Creates a new static site with starter templates and example content.
mix alkali.new my_blog
Options:
--path- Custom path for the new site (default: current directory)
mix alkali.build [path]
Builds the static site.
# Build current directory
mix alkali.build
# Build specific path
mix alkali.build ~/my_blog
Options:
--draft/-d- Include draft posts--verbose/-v- Show detailed build output--clean/-c- Clean output directory before building
mix alkali.post <title> [path]
Creates a new blog post with pre-filled frontmatter.
mix alkali.post "My Post Title"
This creates content/posts/2024-01-15-my-post-title.md with:
- Current date
- Title from argument
- Default frontmatter fields
mix alkali.clean [path]
Removes the _site/ output directory.
mix alkali.clean
Configuration
Create a config/alkali.exs file:
import Config
config :alkali,
site: %{
title: "My Awesome Blog",
url: "https://myblog.com",
author: "Your Name",
description: "A blog about Elixir and web development"
}Deployment
Alkali generates a static _site/ directory that can be deployed anywhere:
Netlify
# Build
mix alkali.build
# Deploy
netlify deploy --prod --dir=_site
Vercel
# Build
mix alkali.build
# Deploy
vercel --prod _site
GitHub Pages
# Build
mix alkali.build
# Copy to docs/ (if using docs/ for GitHub Pages)
cp -r _site/* docs/
# Commit and push
git add docs/
git commit -m "Update site"
git push
Any Static Host
Just upload the contents of _site/ to your web server!
Examples
Example Blog Post
---
title: Getting Started with Elixir
date: 2024-01-15
tags: [elixir, tutorial, beginners]
category: programming
layout: post
---
# Getting Started with Elixir
Elixir is a functional programming language that runs on the Erlang VM...
## Installation
\`\`\`bash
# macOS
brew install elixir
# Ubuntu
sudo apt-get install elixir
\`\`\`
## Your First Program
\`\`\`elixir
IO.puts "Hello, World!"
\`\`\`Example Layout
layouts/post.html.heex:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= @page.title %> - <%= @site[:site_name] %></title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<header>
<h1><a href="/"><%= @site[:site_name] %></a></h1>
</header>
<article>
<h1><%= @page.title %></h1>
<div class="meta">
<%= if @page.date do %>
<time datetime="<%= @page.date %>">
<%= Calendar.strftime(@page.date, "%B %d, %Y") %>
</time>
<% end %>
<%= if @page.category do %>
<span class="category">
<a href="/categories/<%= @page.category %>.html">
<%= @page.category %>
</a>
</span>
<% end %>
</div>
<div class="content">
<%= @content %>
</div>
<%= if length(@page.tags) > 0 do %>
<div class="tags">
Tags:
<%= for tag <- @page.tags do %>
<a href="/tags/<%= tag %>.html"><%= tag %></a>
<% end %>
</div>
<% end %>
</article>
<footer>
<p>© <%= DateTime.utc_now().year %> <%= @site[:author] %></p>
</footer>
</body>
</html>Architecture
Alkali follows Clean Architecture principles:
Domain Layer (Entities)
├── Page - Represents a content page
├── Collection - Represents a group of pages
└── Asset - Represents a static asset file
Application Layer (Use Cases)
├── ParseContent - Parses Markdown files
├── GenerateCollections - Creates tag/category pages
├── ProcessAssets - Minifies and fingerprints assets
└── BuildSite - Orchestrates the entire build
Infrastructure Layer
├── LayoutResolver - Resolves and renders layouts
├── BuildCache - Manages incremental build cache
└── FileSystem - File I/O operations
Interface Layer (Mix Tasks)
├── mix alkali.new - Site generator
├── mix alkali.build - Build command
└── mix alkali.post - Post generatorDevelopment
Running Tests
# Run all tests
mix test
# Run with coverage
mix test --cover
# Run specific test file
mix test test/alkali/application/use_cases/build_site_test.exs
Code Quality
# Format code
mix format
# Run Credo
mix credo
# Run Dialyzer
mix dialyzer
Contributing
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Write tests for your changes
- Ensure all tests pass (
mix test) - Format your code (
mix format) - Commit your changes (
git commit -am 'Add new feature') - Push to the branch (
git push origin feature/my-feature) - Create a Pull Request
License
MIT License - see LICENSE file for details.
Credits
Built with ❤️ using Elixir.
Inspired by:
- Jekyll - Ruby-based static site generator
- Hugo - Go-based static site generator
- Eleventy - JavaScript-based static site generator
Changelog
v0.1.0
Initial Release
- ✅ Markdown content parsing with YAML frontmatter
- ✅ EEx template rendering with layouts
- ✅ Automatic tag and category collections
- ✅ CSS/JS minification and fingerprinting
- ✅ Incremental builds with file change detection
- ✅ Mix tasks for building and content creation
- ✅ 156/156 tests passing (100% coverage)