# Blogatto

[](https://hex.pm/packages/blogatto)
[](https://hexdocs.pm/blogatto/)
[](https://conventionalcommits.org)
[](https://www.erlang.org/)
[](https://github.com/veeso/blogatto/actions/workflows/test.yml)
A Gleam framework for building static blogs with [**Lustre**](https://hexdocs.pm/lustre/) and Markdown.
Blogatto generates your entire static site from a single configuration: blog posts from markdown with frontmatter, static pages from [Lustre](https://hexdocs.pm/lustre/) views, RSS feeds, sitemaps, and robots.txt — all rendered via [Maud](https://hexdocs.pm/maud/) components.
## Features
- Blog posts from markdown files with YAML frontmatter
- Multilingual posts via `index-{lang}.md` file naming convention
- Static pages from [Lustre](https://hexdocs.pm/lustre/) view functions
- RSS feed generation with customizable filtering and serialization
- Sitemap XML generation with alternate language links
- Robots.txt generation
- Static asset copying
- Custom Maud components for markdown rendering
- Configurable blog post templates
- Dev server with file watching, auto-rebuild, and live reload
## Installation
```sh
gleam add blogatto@2
gleam add lustre@5
```
## Quick start
```gleam
import blogatto
import blogatto/config
import blogatto/config/feed
import blogatto/config/markdown
import blogatto/config/robots
import blogatto/config/sitemap
import blogatto/error
import blogatto/post.{type Post}
import gleam/io
import gleam/list
import gleam/time/timestamp
import lustre/attribute
import lustre/element.{type Element}
import lustre/element/html
const site_url = "https://example.com"
pub fn main() {
// Markdown config with custom heading component
let md =
markdown.default()
|> markdown.markdown_path("./blog")
|> markdown.route_prefix("blog")
|> markdown.h1(fn(id, children) {
html.h1([attribute.id(id), attribute.class("post-title")], children)
})
// RSS feed
let rss =
feed.new("My Blog", site_url, "My personal blog")
|> feed.language("en-us")
|> feed.generator("Blogatto")
// Build configuration
let cfg =
config.new(site_url)
|> config.output_dir("./dist")
|> config.static_dir("./static")
|> config.markdown(md)
|> config.route("/", home_view)
|> config.feed(rss)
|> config.sitemap(sitemap.new("/sitemap.xml"))
|> config.robots(robots.RobotsConfig(
sitemap_url: site_url <> "/sitemap.xml",
robots: [
robots.Robot(
user_agent: "*",
allowed_routes: ["/"],
disallowed_routes: [],
),
],
))
case blogatto.build(cfg) {
Ok(Nil) -> io.println("Site built successfully!")
Error(err) -> io.println("Build failed: " <> error.describe_error(err))
}
}
fn home_view(posts: List(Post(Nil))) -> Element(Nil) {
let sorted =
list.sort(posts, fn(a, b) { timestamp.compare(b.date, a.date) })
html.html([], [
html.head([], [html.title([], "My Blog")]),
html.body([], [
html.h1([], [element.text("My Blog")]),
html.ul(
[],
list.map(sorted, fn(p) {
html.li([], [
html.a([attribute.href("/blog/" <> p.slug)], [
element.text(p.title),
]),
])
}),
),
]),
])
}
```
Running `gleam build` will generate the `dist` directory with the following structure:
```
dist/
├── blog/
│ └── my-post/
│ └── index.html
├── index.html
├── robots.txt
├── sitemap.xml
└── feed.xml
```
## Dev server
Blogatto includes a built-in development server that watches your source files for changes, automatically rebuilds the site, and live-reloads the browser via SSE.
Create a separate dev entrypoint module (e.g., `src/dev.gleam`):
```gleam
import blogatto/dev
import blogatto/error
import gleam/io
import my_blog // your module that exposes your blogatto config
pub fn main() {
let cfg = my_blog.config()
case
cfg
|> dev.new()
|> dev.build_command("gleam run -m my_blog")
|> dev.port(3000)
|> dev.start()
{
Ok(Nil) -> io.println("Dev server stopped.")
Error(err) -> io.println("Dev server error: " <> error.describe_error(err))
}
}
```
Run with: `gleam run -m dev`
The dev server will:
1. Perform an initial build by running the configured build command
2. Serve the output directory over HTTP at `http://127.0.0.1:3000`
3. Watch `src/`, markdown paths, and static assets for changes
4. Debounce rapid file changes (~300ms) and rebuild automatically
5. Live-reload the browser on successful rebuilds
### Configuration
| Option | Default | Description |
|--------|---------|-------------|
| `build_command` | `"gleam run"` | Shell command to rebuild the site |
| `port` | `3000` | HTTP server port |
| `host` | `"127.0.0.1"` | Bind address |
| `live_reload` | `True` | Inject live-reload script into HTML responses |
> **Note for Linux users**: The file watcher requires `inotify-tools` to be installed.
## Documentation
Full documentation is available at [blogat.to](https://blogat.to), covering blog post structure, configuration, markdown components, static pages, RSS feeds, sitemaps, dev server, and error handling.
API reference is on [HexDocs](https://hexdocs.pm/blogatto/).
## Development
```sh
gleam build # Compile the project
gleam test # Run the tests
gleam format src test # Format code
```
## License
Blogatto is licensed under the MIT License. See [LICENSE](LICENSE) for details.