Astral's long-term goal is to compete with Astro as an Elixir-native static site and hybrid site framework, while keeping Volt focused on frontend assets, bundling, dev server, and HMR primitives.

Architecture principles

  • Volt stays Vite-like. Volt owns JavaScript/TypeScript/CSS compilation, bundling, assets, virtual modules, dev server primitives, and HMR.
  • Astral owns site semantics. Astral owns pages, routes, content collections, layouts, frontmatter, feeds, sitemap, metadata, islands, and deployment adapters.
  • Elixir-first configuration. Site config should be real Elixir returning structs, not global app env or JavaScript object clones.
  • Parser-backed processing. Use real parsers for Markdown, HTML-like markup, JavaScript/TypeScript, CSS, and frontmatter.
  • Value before abstraction. Add hooks and APIs when they unlock user-facing capabilities.

Plugin architecture direction

Astral plugins should feel familiar to Volt plugin authors.

Volt's plugin design has several properties worth preserving:

  • a behaviour module (Volt.Plugin),
  • required name/0,
  • mostly optional callbacks,
  • plugins configured as modules or {module, opts} tuples,
  • opts passed by supporting one extra callback arity,
  • Vite-style enforce/0 ordering with :pre, normal, and :post,
  • a runner module that centralizes ordering and optional callback dispatch,
  • first-match hooks for ownership decisions,
  • pipeline hooks that transform data in sequence.

Astral now has the initial plugin foundation on master after v0.1.0: Astral.Plugin, Astral.PluginRunner, config DSL support, Volt-style ordering, and tuple opts support. The first hooks are intentionally focused on the existing pipeline: config/1, build_start/1, site_discovered/1, render_page/3, and build_done/1.

Future plugins should grow into richer site-level hooks instead of asset-level hooks:

defmodule MySite.Plugin do
  @behaviour Astral.Plugin

  def name, do: "my-site-plugin"
  def enforce, do: :pre

  def collections(config), do: [%Astral.Collection{name: :posts, dir: "content/posts"}]
  def routes(site), do: [%Astral.Route{path: "/feed.xml", kind: :generated}]
  def render_route(route, site), do: {:ok, xml}
end

Likely next plugin callbacks:

  • collections/1 — provide content collection definitions.
  • load_content/3 — load/normalize content entries for plugin-owned formats.
  • routes/1 — add generated routes such as feeds, sitemaps, tag pages, or pagination pages. (Initial foundation landed on master after v0.1.0.)
  • render_route/2 — render plugin-owned generated routes. (Initial foundation landed on master after v0.1.0.)
  • transform_markdown/3 — transform MDEx document or rendered Markdown before layout.
  • transform_html/3 or expanded render_page/3/route hooks — transform rendered route HTML before writing/serving.
  • head/2 — contribute metadata/link/script tags.
  • dev_server/2 — optional dev-only Plug composition or route hooks.

Implementation should mirror Volt's runner discipline:

  • Astral.Plugin behaviour with optional callbacks.
  • Astral.PluginRunner to normalize modules / {module, opts} tuples.
  • Stable plugin ordering through enforce/0.
  • Callback extra-arity opts support.
  • No ad hoc plugin calls scattered through builder/dev/discovery modules.

Milestones

v0.2 — Content framework

Goal: make Astral useful for blogs, docs, changelogs, and small content-heavy sites.

  • Content collections. (Initial discovery landed on master after v0.1.0.)
  • Collection schemas and validation. JSONSpec-generated JSON Schema maps are the preferred schema format; Zoi schemas are also supported. (Initial adapter landed on master after v0.1.0.)
  • Draft support.
  • Slugs and permalinks.
  • Dynamic routes such as pages/blog/[slug].md.
  • Generated routes from collections. (Initial collection entry routes landed on master after v0.1.0.)
  • Pagination. (Route patterns, pagination pages, route conversion, and collection pagination plugin landed on master after v0.1.0.)
  • Tags and categories. Keep userland until a proven abstraction emerges; Astro treats these as ordinary dynamic routes rather than a core taxonomy API.
  • RSS/Atom feed generation. (Initial Atom feed plugin and option polish landed on master after v0.1.0.)
  • Sitemap generation. (Initial sitemap plugin and option polish landed on master after v0.1.0.)
  • Collection query helpers. (Initial entries/published/sort/tags helpers landed on master after v0.1.0.)
  • Frontmatter defaults.
  • Table of contents and heading anchors. *(Initial Markdown heading extraction and anchors landed on master after v0.1.0.)

v0.3 — Plugin foundation

Goal: make content/build behavior extensible without hard-coding every SSG feature into core.

  • Astral.Plugin behaviour modeled after Volt.Plugin. (Initial foundation landed on master after v0.1.0.)
  • Astral.PluginRunner with Volt-style optional callback dispatch. (Initial foundation landed on master after v0.1.0.)
  • Config DSL support for plugins. (Initial foundation landed on master after v0.1.0.)
  • Plugin docs and examples. (Initial README coverage landed on master after v0.1.0.)
  • Built-in feed/sitemap functionality either as internal plugins or plugin-shaped modules. (Initial plugin-shaped modules landed on master after v0.1.0.)
  • Extractable XML DSL foundation. (Extracted to separate xm package on master after v0.1.0.)
  • Generated route hooks for feeds/sitemaps/pagination. *(Initial routes/1 + render_route/2 hooks landed on master after v0.1.0.)
  • Elixir-style route patterns for generated routes, e.g. /blog/:slug and /blog/*page. *(Landed on master after v0.1.0.)
  • Collection pagination plugin built on generated route hooks. (Landed on master after v0.1.0.)
  • Compatibility story for passing Astral-generated virtual entries/modules to Volt.

v0.4 — HEEx components

Goal: provide an Elixir-native component authoring story before inventing a custom file format.

  • .heex pages and layouts.
  • Function components.
  • Slots.
  • Compile-time template checks where practical.
  • Shared assigns and helpers for layouts/pages.
  • Metadata/head helpers.

v0.5 — Islands MVP

Goal: deliver Astro's biggest differentiator in an Elixir/Volt-native way.

  • Island declarations in HEEx/EEx layouts/pages.
  • Initial hydration modes:
    • client:load
    • client:idle
    • client:visible
  • Island manifest generation.
  • Prop serialization.
  • Volt virtual entries for island client boot code.
  • One framework adapter first, then expand.

Candidate API direction:

<.island component="Counter" client="visible" props={%{count: 0}} />

v0.6 — Framework adapters and richer islands

  • React islands.
  • Vue islands.
  • Svelte islands.
  • Solid islands.
  • client:media and client:only hydration modes.
  • Route-level and component-level code splitting.
  • Island dev diagnostics.

v0.7 — Documentation/blog product layer

Goal: compete for real docs and blog sites.

  • Starter templates:
    • blog
    • docs
    • marketing
  • Search integration.
  • Syntax highlighting.
  • Related posts.
  • Redirects.
  • Canonical URLs.
  • Social metadata helpers.
  • mix astral.new --template docs.

v0.8 — Hybrid/runtime modes

Goal: move beyond static-only without making Volt responsible for site semantics.

  • Static output mode remains default.
  • Plug runtime adapter.
  • Phoenix integration adapter.
  • Hybrid prerender + dynamic routes.
  • Runtime route manifest.

v1.0 — Stable Astro-class foundation

  • Stable config DSL.
  • Stable content collection API.
  • Stable plugin API.
  • Stable island API.
  • Strong HexDocs and examples.
  • Dogfooded docs site built with Astral.
  • Production deployment guides for common static hosts.

Near-term priority

The next major feature should be content collections + dynamic routes, with the plugin system designed in parallel so feeds, sitemap, pagination, and future integrations can be implemented plugin-first instead of as one-off core features.