# Astral ✨
[](https://hex.pm/packages/astral) [](https://hexdocs.pm/astral)
Volt-powered static site generation for Elixir. Astral owns site semantics — pages, routes, Markdown, frontmatter, layouts, public files, and static HTML output — while [Volt](https://hex.pm/packages/volt) handles TypeScript, CSS, assets, dev-server integration, and HMR.
```bash
mix igniter.install astral
mix astral.dev
mix astral.build
```
Astral is intentionally separate from Volt. Volt remains the Vite-like frontend toolchain; Astral is the site framework built on top.
## Why Astral
Static site generators often force site configuration, content rules, and frontend tooling into JavaScript. Astral keeps the site layer in ordinary Elixir while reusing Volt's BEAM-native asset pipeline.
You get:
- Elixir `astral.config.exs` instead of JavaScript config objects.
- Markdown pages rendered with MDEx and YAML frontmatter.
- EEx layouts with `@content`, `@page`, `@metadata`, `@route`, and `@site` assigns.
- Per-page layout selection through frontmatter.
- Plain HTML pages for simple routes.
- Public static files copied as-is.
- TypeScript/CSS/assets built and served by Volt.
- A Plug/Bandit dev server with Volt HMR client injection and full reloads for pages/layouts/public files.
- Igniter-powered starter scaffolding.
## Status
Astral is early, but the first release is useful for small static sites and documentation prototypes. Collections, feeds, sitemap generation, and richer routing are intentionally left for follow-up releases.
## Installation
Install into an existing Mix project with Igniter:
```bash
mix igniter.install astral
```
Or add the dependency manually:
```elixir
def deps do
[
{:astral, "~> 0.1.0"}
]
end
```
Then scaffold a starter site:
```bash
mix astral.new
```
The scaffold creates `astral.config.exs`, starter Markdown pages, an EEx layout, TypeScript/CSS assets, public files, `tsconfig.json`, and Volt JS/TS formatting/linting configuration.
## Project layout
```text
astral.config.exs
pages/
index.md
about.md
layouts/
default.html
assets/
app.ts
styles.css
public/
robots.txt
```
## Configuration
Astral config is real Elixir and returns an `%Astral.Config{}` struct. No global app env is required for site settings.
```elixir
# astral.config.exs
import Astral.Config
site do
root "."
outdir "dist"
pages "pages"
public "public"
layouts "layouts" do
default "default.html"
end
assets "assets" do
entry "app.ts"
url_prefix "/assets"
end
end
```
## Pages and frontmatter
Markdown pages are rendered with MDEx. YAML frontmatter is extracted by MDEx and decoded with YamlElixir:
```markdown
---
title: About Astral
permalink: /about-us/
layout: default.html
---
# About
```
Output routes:
```text
pages/index.md -> dist/index.html
pages/about.md -> dist/about/index.html
pages/blog/post.html -> dist/blog/post/index.html
```
`permalink` overrides the default route. `layout` selects a layout from the layouts directory. Use `layout: false` to render without a layout.
Plain `.html` files in `pages/` are supported too.
## Layouts
Layouts are EEx templates. Use `@content` where page HTML should be inserted:
```html
<!doctype html>
<html lang="en">
<head>
<title><%= @page.title || "Astral" %></title>
<script type="module" src="<%= Astral.asset_path(@site, "app.ts") %>"></script>
</head>
<body>
<main data-route="<%= @route %>">
<%= @content %>
</main>
</body>
</html>
```
Available assigns:
- `@content` — rendered page HTML.
- `@page` — `%Astral.Content{}` for the current page.
- `@metadata` — decoded frontmatter map.
- `@route` — route path such as `/about/`.
- `@site` — discovered `%Astral.Site{}`.
## Assets
Astral delegates assets to Volt. Reference source assets from layouts with `Astral.asset_path/2`:
```eex
<script type="module" src="<%= Astral.asset_path(@site, "app.ts") %>"></script>
```
In development this returns the source path served by Volt, for example `/assets/app.ts`. In static builds it reads Volt's manifest and returns the emitted file, for example `/assets/app-5e6f7a8b.js`.
Volt content hashes are enabled by default. For examples or prototypes that need stable filenames:
```elixir
assets "assets" do
entry "app.ts"
url_prefix "/assets"
hash false
end
```
## Development server
```bash
mix astral.dev
mix astral.dev --open
mix astral.dev --config astral.config.exs --port 4000
```
The dev server:
- serves Astral routes,
- serves public files,
- delegates Volt asset/HMR routes to `Volt.DevServer`,
- injects Volt's HMR client into rendered HTML,
- watches pages/layouts/public files for full reloads,
- renders useful HTML error pages for Markdown/layout/config failures.
## Static builds
```bash
mix astral.build
```
Example output:
```text
[Astral] Built 2 page(s) into dist
Routes:
/ dist/index.html
/about/ dist/about/index.html
Assets:
dist/assets/manifest.json
```
Upload `dist/` to any static host or CDN. See [`guides/deployment.md`](guides/deployment.md) for production asset behavior and deployment notes.
## Example site
A runnable example lives in `examples/basic`:
```bash
cd examples/basic
mix deps.get
mix astral.dev
mix astral.build
mix check
```
It demonstrates Markdown, HTML pages, layouts, public files, Volt TypeScript/CSS assets, and Volt JS/TS formatting/linting.
## Programmatic API
```elixir
Astral.build(config: "astral.config.exs")
Astral.dev(config: "astral.config.exs", port: 4000)
Astral.asset_path(site, "app.ts")
```
## Development
```bash
mix deps.get
mix ci
```
## License
MIT © 2026 Danila Poyarkov