# Assets
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:
```astral
<.image src="images/hero.jpg" alt="Hero" width={1200} format={:webp} quality={82} />
```
Ordinary local Markdown images are optimized too:
```md

```
`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:
```text
assets/images/hero.jpg
-> dist/assets/hero-1200x800-k4L9v8B2qa.webp
```
Responsive pictures generate multiple variants and a fallback image:
```astral
<.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:
```astral
<.figure
src={@entry.data.cover}
alt={@entry.data.title}
caption={@entry.data.description}
width={800}
/>
```
Or pass rich caption content through the default slot:
```astral
<.figure src="images/chart.png" alt="Revenue by month" width={800}>
<strong>Figure 1.</strong> Monthly revenue.
</.figure>
```
Configure defaults with the `image` option:
```elixir
site do
image quality: 82,
widths: [480, 768, 1200, 1600],
formats: [:webp],
fallback_format: :jpeg
end
```
Files 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:
```astral
<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:
```astral
---
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:
```elixir
site do
image do
allow_remote "https://images.example.com/**"
allow_remote "https://**.amazonaws.com/bucket/**"
end
end
```
Allowed remote images flow through the same `<.image>`, `<.picture>`, and `<.figure>` components:
```astral
<.image src="https://images.example.com/hero.jpg" alt="Hero" width={800} />
```
Remote pattern wildcards follow Astro's model:
- `*.example.com` matches one subdomain level.
- `**.example.com` matches 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:
```astral
<.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
```elixir
site do
assets "assets" do
entry "app.ts"
url_prefix "/assets"
end
end
```
The source root is `assets/`; the browser URL prefix is `/assets`.
## Reference assets from layouts
Use `Astral.asset_path/2` with the source entry name:
```eex
<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:
```text
app.ts -> app-5e6f7a8b.js
styles.css -> styles-1a2b3c4d.css
```
Disable hashes for examples or prototypes that need stable filenames:
```elixir
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:
```astral
<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.