Minimal site

import Astral.Config

site do
  pages "pages"

  layouts "layouts" do
    default "default.html"
  end
end

Common paths

site do
  root "."
  outdir "dist"
  pages "pages"
  public "public"
  components "components"
end

Layouts

layouts "layouts" do
  default "default.html"
end

Use a .astral layout:

layouts "layouts" do
  default "site.astral"
end

Assets

assets "assets" do
  entry "app.ts"
  url_prefix "/assets"
  hash true
end

Reference asset entries from layouts with Astral.asset_path/2. In development this points at Volt's dev server; in static builds it resolves through the emitted manifest.

Browser environment variables

config :volt, env_prefix: ["VOLT_", "PUBLIC_"]

Variables matching env_prefix are exposed to browser assets through import.meta.env. Keep secrets outside exposed prefixes.

Dynamic file routes

pages/blog/[slug].astral   -> /blog/:slug
pages/docs/[...path].md    -> /docs/*path

Read route params from templates and layouts with string keys:

<%= @params["slug"] %>

Declare arbitrary dynamic .astral paths with the path/1 setup helper:

---
paths = [path tag: "elixir", assigns: %{title: "Elixir"}]
---
<h1>{@title}</h1>

Static generated routes

get "/robots.txt", content_type: "text/plain" do
  "User-agent: *\nAllow: /\n"
end

Multilingual static routes

Use localized folders and site-owned link helpers today:

pages/about.md     -> /about/
pages/es/about.md  -> /es/about/

First-class i18n routing config, fallbacks, domains, and browser-language middleware are not implemented yet.

Use plug only for config-generated route middleware:

plug MySite.GeneratedHeaders, cache: "public, max-age=3600"

Content collections

collections do
  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: []
    end
  end
end

Site metadata

Keep SEO and document metadata in layouts or components:

<head>
  <.base_head title={@page.title || "My Site"} route={@route} />
</head>

Pass deployment URLs to feed, sitemap, or head components where they are needed.

Plugins

plugins [
  {Astral.Plugin.Feed,
   site_url: "https://example.com",
   title: "My Blog",
   author: "Me",
   collection: :posts},
  {Astral.Plugin.Sitemap,
   site_url: "https://example.com"}
]