Static site generation for Elixir. Astral gives you Astro-class site features — pages, Markdown, layouts, content collections, pagination, feeds, sitemaps, and component templates — while Volt handles TypeScript, CSS, assets, dev serving, and HMR.
mix igniter.install astral
mix astral.dev
mix astral.buildBuild docs, blogs, marketing pages, and content sites with Elixir config and templates. No JavaScript site config, no separate bundler process, no Node.js requirement for the default toolchain.
Most static site generators put your content model, routing, and build configuration in JavaScript. Astral keeps the site layer in Elixir and delegates frontend assets to Volt.
You get the pieces expected from a modern static site framework:
- File-based static pages from Markdown, HTML, and
.astraltemplates. - Markdown content with HEEx-style local components through MDEx.
- Dynamic file routes such as
pages/blog/[slug].astralandpages/docs/[...path].md. - HEEx-first
.astralpages, layouts, and local components. - Schema-backed content collections with Ecto-style fields, JSONSpec maps, or Zoi schemas.
- Static pagination and generated routes for blogs, docs, and indexes.
- Built-in feed and sitemap plugins.
- Stable Markdown heading anchors for table-of-contents layouts.
- Optimized build-time images with
<.image>,<.picture>, and<.figure>components. - Client-only islands for Volt-powered framework components.
- Public files copied as-is.
- TypeScript, CSS, imported assets, browser environment variables, dev serving, and HMR through Volt.
- Plug/Bandit dev server with full reloads for pages, layouts, components, and public files.
- Igniter-powered starter scaffolding.
Astral is early but usable for small static sites, documentation prototypes, and blogs. See the roadmap for planned work.
astral.config.exs is ordinary Elixir:
import Astral.Config
layouts do
default "site.astral"
end
assets do
entry "app.ts"
url_prefix "/assets"
endSee the Getting Started guide and Configuration cheatsheet.
.astral templates use Phoenix HEEx syntax but render static HTML:
---
assigns = assign(assigns, :title, "Home")
---
<h1>{@title}</h1>
<.pill :for={feature <- @features}>{feature}</.pill>
Local components and slots use HEEx conventions:
<!-- components/card.astral -->
<article class="card">
{render_slot(@inner_block)}
</article>
Browser assets inside .astral templates are extracted into Volt's asset graph:
<style>.hero { padding: 4rem; }</style>
<script lang="ts">console.log("ready")</script>
Markdown can use the same local components:
# Project
<.card>
Rendered inside Markdown by MDEx and HEEx.
</.card>See the .astral Templates guide and Pages and Layouts guide.
Define typed content collections in Elixir:
collection :posts, "content/posts" do
permalink "/blog/:slug/"
layout "post.html"
schema do
field :title, :string, required: true
field :date, :date, required: true
field :draft, :boolean, default: false
field :tags, {:array, :string}, default: []
field :cover, :image
end
endImage fields resolve relative to their entry file, expose dimensions and format, and can be passed directly to <.image>, <.picture>, or <.figure>.
Allow trusted remote image optimization with URL-shaped policies:
image do
allow_remote "https://images.example.com/**"
endUse validated data from layouts and templates:
<%= for post <- @collections.posts do %>
<a href={post.route_path}><%= post.data.title %></a>
<% end %>Collection-backed dynamic file routes let page templates own the detail page HTML:
content/posts/hello.md
pages/blog/[slug].astral
See the Content Collections guide and Pages and Layouts guide.
Build common site routes with plugins:
plugin Astral.Plugin.CollectionPages,
collection: :posts,
pattern: "/blog/*page",
page_size: 10,
layout: "blog.html"
plugin Astral.Plugin.Feed,
site_url: "https://example.com",
title: "My Blog",
author: "Me",
collection: :posts
plugin Astral.Plugin.Sitemap,
site_url: "https://example.com"Add one-off generated files directly in config with Phoenix-shaped get routes:
get "/robots.txt", content_type: "text/plain" do
"User-agent: *\nAllow: /\n"
end
get "/search-index.json", content_type: "application/json" do
Jason.encode!(MySite.Search.index(site))
endUse Astral plugins for site semantics and Volt plugins for browser asset integrations. See Pagination and Generated Routes, Feeds and Sitemaps, and Plugins and Integrations.
Render optimized images from .astral pages or component-aware Markdown:
<.image src="images/hero.jpg" alt="Hero" width={1200} format={:webp} />
<.picture
src="images/hero.jpg"
alt="Hero"
widths={[480, 768, 1200]}
formats={[:webp, :avif]}
/>
<.figure src="images/hero.jpg" alt="Hero" caption="Product hero" width={1200} />
Astral writes compressed, content-hashed variants to dist/assets/ during static builds. Local Markdown image syntax is optimized too:
Include trusted local SVG files inline when you need definitions, masks, or hand-authored SVG markup:
<.svg src="@/icons/clip-paths.svg" class="sr-only" />
Reference source frontend assets from layouts:
<script type="module" src="<%= Astral.asset_path(@site, "app.ts") %>"></script>In development this points to Volt's dev server. In static builds it resolves through Volt's manifest to content-hashed output files.
See the Assets guide, Editor Setup and TypeScript guide, Environment Variables guide, and the Volt documentation for frontend tooling details.
Render Iconify icons server-side with PhoenixIconify; Astral prepares the icon manifest during build/dev rendering:
<.icon name="ri:external-link-fill" class="inline-block" width="12" height="12" />
Mount a browser component from your Volt assets:
<.vue
component="islands/Gallery.vue"
client={:visible}
props={%{images: @images}}
>
<div class="thumbnail-strip">Static HEEx children become the default framework slot.</div>
</.vue>
Astral provides framework-specific island components for every framework Volt supports: <.vue>, <.svelte>, <.react>, and <.solid>. All adapters are enabled by default; configure islands do adapter :vue end only if you want to restrict the allowed set. Client directives include :load, :idle, :visible, and :media with a media query string. The first island milestone is client-only: Astral renders a container, static slot template, and generated entry module, while Volt compiles the imported framework component.
mix astral.dev --open
mix astral.buildmix astral.dev serves routes, public files, Volt assets, HMR, and useful HTML error pages. mix astral.build writes static files to dist/ for any static host or CDN.
See the Development Server guide and Static Builds guide.
A runnable example lives in examples/basic:
cd examples/basic
mix deps.get
mix astral.dev
mix astral.build
mix checkIt demonstrates Markdown, HTML pages, .astral pages/layouts/components, public files, Volt TypeScript/CSS assets, feeds, sitemaps, and Volt JS/TS formatting/linting.
Full documentation, guides, and cheatsheets are available on HexDocs.
mix deps.get
mix ciMIT © 2026 Danila Poyarkov