Astral has two asset paths:
- Astral owns site images rendered with
<.image>and<.picture>. - Volt owns browser assets such as TypeScript, JavaScript, CSS, and files imported by them.
Optimized images
Use built-in HEEx components from .astral pages, layouts, components, and component-aware Markdown:
<.image src="images/hero.jpg" alt="Hero" width={1200} format={:webp} quality={82} />Ordinary local Markdown images are optimized too:
src resolves from the configured image source directories. By default, Astral looks under assets/, the site root, and public/.
Static builds write compressed, content-hashed files into the configured asset output directory:
assets/images/hero.jpg
-> dist/assets/hero-1200x800-k4L9v8B2qa.webpResponsive pictures generate multiple variants and a fallback image:
<.picture
src="images/hero.jpg"
alt="Hero"
widths={[480, 768, 1200]}
formats={[:webp, :avif]}
fallback_format={:jpeg}
sizes="(max-width: 768px) 100vw, 1200px"
/>The image pipeline is backed by the Elixir Image package and libvips. Output filenames include source content and transform options, so changing the source, dimensions, format, quality, or fit creates a new browser URL. Markdown image syntax uses the default image format and original dimensions unless you use <.image>, <.picture>, or <.figure> for explicit transforms.
Use <.figure> for semantic captions:
<.figure
src={@entry.data.cover}
alt={@entry.data.title}
caption={@entry.data.description}
width={800}
/>Or pass rich caption content through the default slot:
<.figure src="images/chart.png" alt="Revenue by month" width={800}>
<strong>Figure 1.</strong> Monthly revenue.
</.figure>Configure defaults with the image option:
site do
image quality: 82,
widths: [480, 768, 1200, 1600],
formats: [:webp],
fallback_format: :jpeg
endFiles in public/ are still copied as-is. Use <.image>, <.picture>, or <.figure> when you want Astral to optimize and hash an image.
Resolved collection image fields expose dimensions and format:
<p>{@entry.data.cover.width} × {@entry.data.cover.height} {@entry.data.cover.format}</p>Use Astral.Image.metadata/1 when you need metadata for an image path during rendering:
---
assigns = assign(assigns, :hero, Astral.Image.metadata("images/hero.jpg"))
---
<p>{@hero.width} × {@hero.height} {@hero.format}</p>During development, Astral emits /_astral/image/... URLs and generates optimized images on demand into the image cache. Responses use no-cache headers like Volt's dev asset server, so browser refreshes reflect source changes.
Remote images
Astral does not optimize arbitrary remote URLs by default. Allow trusted remote image sources explicitly:
site do
image do
allow_remote "https://images.example.com/**"
allow_remote "https://**.amazonaws.com/bucket/**"
end
endAllowed remote images flow through the same <.image>, <.picture>, and <.figure> components:
<.image src="https://images.example.com/hero.jpg" alt="Hero" width={800} />Remote pattern wildcards follow Astro's model:
*.example.commatches one subdomain level.**.example.commatches any subdomain depth./assets/*matches one nested path segment./assets/**matches any nested path below/assets/.
Remote redirects are followed only when every destination also matches an allow_remote pattern. Astral caches the downloaded original in the image cache and reuses response validators such as ETag and Last-Modified when available.
Static builds fetch allowed remote originals during generation. Because the original is available at build time, Astral can infer a missing dimension from the remote image metadata:
<.image src="https://images.example.com/hero.jpg" alt="Hero" width={800} />Development mirrors Astro's endpoint model: page rendering emits /_astral/image/... without downloading the remote original, and the remote request happens when the browser asks for that optimized image. In dev, specify both width and height for remote images so Astral can avoid fetching during page render.
Volt browser assets
Astral delegates browser assets to Volt.
Configure Volt assets
site do
assets "assets" do
entry "app.ts"
url_prefix "/assets"
end
endThe source root is assets/; the browser URL prefix is /assets.
Reference assets from layouts
Use Astral.asset_path/2 with the source entry name:
<script type="module" src="<%= Astral.asset_path(@site, "app.ts") %>"></script>In development this returns a source URL served by Volt, such as /assets/app.ts. In static builds it reads Volt's manifest and returns the emitted output path, such as /assets/app-5e6f7a8b.js.
Content hashes
Content hashes are enabled by default for production caching:
app.ts -> app-5e6f7a8b.js
styles.css -> styles-1a2b3c4d.cssDisable hashes for examples or prototypes that need stable filenames:
assets "assets" do
entry "app.ts"
hash false
end.astral template assets
<style> and <script> blocks in .astral templates are extracted as Volt embedded modules:
<style>
.card { border: 1px solid currentColor; }
</style>
<script lang="ts">
console.log("loaded");
</script>Volt builds those blocks alongside the configured asset entry.
Volt features
For TypeScript, CSS, imported assets, asset query modes, workers, code splitting, HMR, and formatting/linting, see the Volt documentation.