# Images and Fonts

Course: [Astro](https://www.devtutorials.ai/learn/astro) · Section: Content
Source: https://www.devtutorials.ai/learn/astro/content/images-and-fonts

> Images in public/ and src/, the Image and Picture components, local and remote images, authorized domains, image sizes, responsive layouts, fit and position, images in Markdown and collections, getImage(), SVG components, image services and the Fonts API.

## Images in Astro

- Images are often the heaviest part of a page. A large photo can be bigger than all the HTML, CSS and JavaScript together.
- Astro has built-in tools that make images smaller and load them at the right time.
- **Image optimisation** = making an image file smaller (resize, compress, modern format) without visible loss.
- In this lesson, you will add optimised images to Asha's site, and load a custom font.

**Simple lesson:** use Astro's image tools, and your images get smaller and safer to load.

### A Real-World Example

- Asha adds a 4000 × 3000 photo from her phone as a post cover. The file is 4 MB.
- On the page, the photo is only shown 800 pixels wide.
- With a plain `<img>`, every reader downloads all 4 MB.
- With Astro's `<Image />`, the build makes an 800-pixel-wide version in a modern format, often under 100 KB.

Press **▶ Run**. Then open **HTML sent to browser** and look at the `<img>` tag.

<p>The image is {cover.width} × {cover.height} in the file.</p>
`,
    "src/assets/cover.svg": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 400">
    <rect width="600" height="400" fill="#ede9fe"/>
    <rect x="150" y="120" width="300" height="180" rx="10" fill="#7c3aed"/>
    <rect x="170" y="140" width="260" height="140" fill="#c4b5fd"/>
    <rect x="120" y="300" width="360" height="16" rx="6" fill="#4c1d95"/>
    <circle cx="500" cy="300" r="22" fill="#f59e0b"/>
</svg>
`,
  }}
/>

What happened:

- `import cover from "../assets/cover.svg"` gave Astro the image's file and its size.
- `width={300}` was given. Astro worked out `height="200"`, to keep the shape.
- Astro added `loading="lazy"` and `decoding="async"` for you.

### What You Will Learn

In this lesson, you will learn:

1. Where to keep images: `public/` or `src/`.
2. How `<Image />` and `<Picture />` work.
3. How to show remote images, and allow their domains.
4. How image sizes and responsive layouts work.
5. How to use images in Markdown and collections.
6. What `getImage()`, SVG components and image services are.
7. How to load fonts with the Fonts API.

## Images in public/ vs src/

### src/ Images

- Keep most images in `src/`, such as `src/assets/`.
- Import them: `import cover from "../assets/cover.jpg";`.
- Astro knows their size and **optimises** them at build time.
- The file name gets a hash, such as `cover.B2x9a.webp`, so browsers can cache it for a long time.

### public/ Images

- Images in `public/` are copied as they are. They are **not** optimised.
- Use them with a plain path: `src="/photo.jpg"`.
- Use `public/` only when a file must keep its exact name, such as `favicon.svg` or a social image.

Default to `src/assets/` for images. Only move an image to `public/` when something outside your code needs its exact address.

**Simple lesson:** images in `src/` are optimised. Images in `public/` are copied as they are.

## The Image and Picture Components

### The Image Component

- `<Image />` comes from `astro:assets`.
- It needs `src` and `alt`.
- **alt** = a short text that describes the image, for screen readers and search engines. Use `alt=""` for a purely decorative image.
- Astro stops the build if `alt` is missing. This protects your readers.

### Image Dimensions

- `width` and `height` set the size of the image Astro makes.
- For a local image, give one of them and Astro works out the other. Give none, and Astro uses the file's own size.
- The browser uses both values to keep space for the image before it loads. The page does not jump.

### The Picture Component

- `<Picture />` makes a `<picture>` element with several formats.
- `formats={["avif", "webp"]}` lists the formats to offer, best first.
- Each browser picks the first format it supports. Older browsers fall back to the `<img>` inside.

<figure className="ast-fig ngc-fig">
<svg viewBox="0 0 640 200" role="img" aria-labelledby="fig-img" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-img">Animation: a 4 MB photo goes through astro build and comes out as a 90 KB WebP and a 60 KB AVIF.</title>
  <defs><marker id="im-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0 0 L10 5 L0 10 z" style={{ fill: "var(--ngc-slate)" }} /></marker></defs>
  <rect x="20" y="50" width="150" height="100" rx="8" style={{ fill: "var(--ngc-red-soft)", stroke: "var(--ngc-red)" }} />
  <text x="95" y="92" textAnchor="middle" style={{ fill: "var(--ngc-red)", fontFamily: "var(--mono)", fontSize: 13, fontWeight: 700 }}>photo.jpg</text>
  <text x="95" y="114" textAnchor="middle" style={{ fill: "var(--ngc-red)", fontFamily: "var(--sans)", fontSize: 12 }}>4000 × 3000 · 4 MB</text>
  <rect x="245" y="70" width="140" height="60" rx="10" style={{ fill: "var(--ngc-orange)" }}><animate attributeName="opacity" values="1;0.7;1" dur="1.5s" repeatCount="indefinite" /></rect>
  <text x="315" y="98" textAnchor="middle" style={{ fill: "#fff", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>astro build</text>
  <text x="315" y="116" textAnchor="middle" style={{ fill: "#fff", fontFamily: "var(--sans)", fontSize: 11 }}>resize + compress</text>
  <rect x="460" y="40" width="160" height="50" rx="8" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)" }} />
  <text x="540" y="62" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--mono)", fontSize: 12, fontWeight: 700 }}>photo.webp</text>
  <text x="540" y="80" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 11 }}>800 × 600 · 90 KB</text>
  <rect x="460" y="110" width="160" height="50" rx="8" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)" }} />
  <text x="540" y="132" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--mono)", fontSize: 12, fontWeight: 700 }}>photo.avif</text>
  <text x="540" y="150" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 11 }}>800 × 600 · 60 KB</text>
  <path d="M170 100 H241" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2, fill: "none" }} markerEnd="url(#im-arrow)" />
  <path d="M385 92 L456 66" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2, fill: "none" }} markerEnd="url(#im-arrow)" />
  <path d="M385 108 L456 134" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2, fill: "none" }} markerEnd="url(#im-arrow)" />
  <circle r="6" style={{ fill: "var(--ngc-red)" }}><animateMotion dur="2.5s" repeatCount="indefinite" path="M170 100 H245" /><animate attributeName="r" values="9;9;4" keyTimes="0;0.8;1" dur="2.5s" repeatCount="indefinite" /></circle>
  <text x="320" y="186" textAnchor="middle" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--sans)", fontSize: 12 }}>Example sizes. Real savings depend on the photo.</text>
</svg>
<figcaption>The build makes small, modern copies of each image.</figcaption>
</figure>

Press **▶ Run**. Compare the `<img>` from `<Image />` with the `<picture>` from `<Picture />`.

<h2>Picture</h2>
`,
    "src/assets/avatar.svg": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
    <circle cx="100" cy="100" r="100" fill="#fde68a"/>
    <circle cx="100" cy="80" r="40" fill="#7c3aed"/>
    <rect x="45" y="130" width="110" height="60" rx="30" fill="#7c3aed"/>
</svg>
`,
  }}
/>

On your computer, Astro uses the **Sharp** library to turn photos into small WebP and AVIF files. This lab runs in the browser, so it keeps the original file and only writes the same HTML attributes. SVG files are already small, so the labs use them.

**Simple lesson:** `<Image />` for one optimised image, `<Picture />` to offer several formats.

## Local and Remote Images

### Remote Images

- **Remote image** = an image from another website, used with a full URL.
- Astro cannot read its size from your project, so give `width` and `height`.
- Or add `inferSize` and Astro downloads the image once to find its size.

### Authorizing Remote Image Domains

- By default, Astro does **not** optimise images from other websites. It shows them as they are.
- To allow it, list the trusted domains in the config:

```js
export default defineConfig({
  image: {
    domains: ["images.unsplash.com"],
  },
});
```

- `image.remotePatterns` allows wider rules, such as every sub-domain of a site.
- This protects your build from being used to process images from anywhere.

**Simple lesson:** remote images need a size, and their domain must be allowed before Astro optimises them.

## Responsive Images

### The layout Property

- **Responsive image** = an image that adapts to the screen size, so phones download small files and big screens get sharp ones.
- Add `layout` to `<Image />` or `<Picture />`:
  - `"constrained"` = shrinks on small screens, never grows past its width. Good for most images.
  - `"full-width"` = always fills the width of its container. Good for banners.
  - `"fixed"` = keeps the exact size. Good for avatars and icons.
  - `"none"` = no responsive behaviour.
- Astro then adds `srcset` and `sizes`. The browser picks the best file for the screen.
- Set a default for the whole site with `image: { layout: "constrained" }` in the config.

### fit and position

- `fit` = how the image fills its box: `"cover"` (crop to fill), `"contain"` (show it all) and others, like CSS `object-fit`.
- `position` = which part to keep when it is cropped: `"top"`, `"center"`, `"left bottom"` and so on.

### Responsive Image Styles

- With a `layout`, Astro adds a small set of styles that make the image behave correctly.
- `image.responsiveStyles` in the config turns them on or off.

Press **▶ Run**. Make the preview narrower by dragging the bar. The constrained image shrinks. The fixed one does not.

<h2>fixed</h2>

<h2>full-width, cropped</h2>
<div style="height: 80px; overflow: hidden;">
</div>
`,
    "src/assets/banner.svg": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 200">
    <rect width="800" height="200" fill="#7c3aed"/>
    <text x="400" y="120" font-size="60" text-anchor="middle" fill="white" font-family="sans-serif">Dev Journal</text>
</svg>
`,
  }}
/>

**Simple lesson:** pick a `layout`, and Astro makes the image fit every screen.

## Images in Content

### Images in Markdown and MDX

- In Markdown, write `![Alt text](./photo.jpg)`.
- A path starting with `./` or `../` is a local image. Astro optimises it like an `<Image />`.
- A path starting with `/` is from `public/` and is not optimised.

### The image() Helper in Collection Schemas

- A post's cover image can be a frontmatter field: `cover: ./cover.jpg`.
- In the schema, use the `image()` helper. The schema becomes a function that receives it:

```ts
schema: ({ image }) =>
  z.object({
    title: z.string(),
    cover: image(),
  }),
```

- `image()` checks that the file exists and turns the path into image data you can pass to `<Image />`.
- The path is relative to the Markdown file.

### [TS] ImageMetadata

- The type of an imported image is `ImageMetadata`: `src`, `width`, `height` and `format`.
- Use it for a component prop: `interface Props { cover: ImageMetadata }`.
- **[JS]** You do not need the type. The object is the same.

      z.object({
        title: z.string(),
        cover: image(),
        coverAlt: z.string(),
      }),
});

`,
    "src/content/blog/my-desk/index.md": `---
title: My study desk
cover: ./desk.svg
coverAlt: A purple desk with a lamp
---

This is where I write every post.
`,
    "src/content/blog/my-desk/desk.svg": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 150">
    <rect width="300" height="150" fill="#ede9fe"/>
    <rect x="40" y="90" width="220" height="12" fill="#7c3aed"/>
    <rect x="60" y="102" width="10" height="40" fill="#7c3aed"/>
    <rect x="230" y="102" width="10" height="40" fill="#7c3aed"/>
    <rect x="200" y="40" width="8" height="50" fill="#f59e0b"/>
    <circle cx="204" cy="40" r="16" fill="#fde68a"/>
</svg>
`,
    "src/pages/index.astro": `---

const posts = await getCollection("blog");
---

{posts.map((post) => (
    <article>
      <h2>{post.data.title}</h2>
    </article>
))}
`,
  }}
/>

**Simple lesson:** use `image()` in a schema for cover images. Pass the result straight to `<Image />`.

## Advanced Image Handling

### getImage()

- `getImage()` makes an optimised image **without** an `<img>` tag.
- Use it when you need just the URL, such as for a CSS background or an Open Graph image.
- `const og = await getImage({ src: cover, width: 1200 });` then use `og.src`.

### Importing SVG Files as Components

- An imported `.svg` file can also be used as a component: `<Logo width={48} />`.
- Astro puts the SVG code straight into the HTML. No extra file is downloaded.
- Props you pass, such as `width`, `height` and `class`, are added to the `<svg>` tag.

### Image Services

- **Image service** = the tool that does the optimising.
- The default is **Sharp**, which runs on your computer at build time.
- `passthroughImageService()` turns optimisation off. Use it on hosts where Sharp cannot run.

Press **▶ Run**. The logo is drawn inline. The banner URL comes from `getImage()`.

    <strong>Dev Journal</strong>
</header>
<div class="hero" style={"background-image: url('" + background.src + "')"}>
    <h1>Notes from my coding journey</h1>
</div>

<style>
    .hero {
      background-size: cover;
      padding: 40px 16px;
      color: white;
    }
</style>
`,
    "src/assets/logo.svg": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#7c3aed"/><path d="M18 46V18h12a14 14 0 0 1 0 28z" fill="white"/></svg>
`,
    "src/assets/hero.svg": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 200"><defs><linearGradient id="g"><stop offset="0" stop-color="#7c3aed"/><stop offset="1" stop-color="#db2777"/></linearGradient></defs><rect width="600" height="200" fill="url(#g)"/></svg>
`,
  }}
/>

**Simple lesson:** `getImage()` gives you just the optimised URL. SVG imports can be inline components.

## Fonts

### The Fonts API Overview

- **Web font** = a font file the browser downloads, so your text looks the same on every device.
- Astro's **Fonts API** downloads font files at build time and serves them from your own site.
- It is faster and more private than loading fonts from another website on every visit.

### Configuring Fonts

- List fonts in `astro.config.mjs` under `fonts`:

```js
import { defineConfig, fontProviders } from "astro/config";

export default defineConfig({
  fonts: [
    {
      provider: fontProviders.google(),
      name: "Poppins",
      cssVariable: "--font-poppins",
      weights: [400, 700],
      fallbacks: ["sans-serif"],
    },
  ],
});
```

### Font Providers

- **Font provider** = where the font files come from.
- Built-in providers include Google Fonts, Fontsource, Bunny and Fontshare.
- `fontProviders.local()` uses font files you keep in your project.

### Using Fonts in Layouts

1. Add `<Font cssVariable="--font-poppins" />` from `astro:assets` inside your layout's `<head>`.
2. Use the variable in CSS: `font-family: var(--font-poppins);`.

### Font Fallbacks and Preloading

- `fallbacks` = fonts to show while the web font loads, or if it fails.
- Astro adjusts the fallback's size to match the web font, so text does not jump when it loads.
- `<Font cssVariable="--font-poppins" preload />` adds **preload** links: the browser starts downloading the font early.
- Preload only the fonts used at the top of the page. Each preload competes with other downloads.

Press **▶ Run**. The `<Font />` component defines the CSS variable. The body uses it.

    <head>
      <meta charset="utf-8" />
    </head>
    <body>
      <slot />
    </body>
</html>

<style is:global>
    body {
      font-family: var(--font-poppins);
    }
</style>
`,
    "src/pages/index.astro": `---
---

    <h1>Dev Journal</h1>
    <p>This text uses the --font-poppins variable.</p>
`,
  }}
/>

In this lab, no font file is downloaded. `<Font />` only sets the variable, so the text shows the fallback (Georgia). On your computer, the real Poppins files are downloaded at build time and served from your site.

**Simple lesson:** list fonts in the config, add `<Font />` to the layout, and use the CSS variable.

## TL;DR

1. **src/ images** = optimised. **public/ images** = copied as they are.
2. **`<Image />`** = needs `src` and `alt`. Adds width, height, lazy loading.
3. **`<Picture />`** = several formats with `formats={["avif", "webp"]}`.
4. **Remote images** = need `width` and `height` (or `inferSize`). Allow domains with `image.domains`.
5. **layout** = `constrained`, `full-width`, `fixed` or `none`. `fit` and `position` control cropping.
6. **image()** = image fields in collection schemas, relative to the Markdown file.
7. **getImage()** = an optimised URL without a tag.
8. **SVG import** = can be a component: `<Logo width={48} />`.
9. **Fonts API** = `fonts` in the config, `<Font cssVariable="..." />` in the head.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| No `alt` | `alt` is optional | Always add `alt`, or `alt=""` for decoration | The build stops |
| Remote image with no size | Astro measures every image | Give `width` and `height`, or `inferSize` | The build stops |
| `src="../assets/x.jpg"` as a string | Strings work like imports | Import the image, then pass the import | The image is not found or not optimised |
| Big photos in `public/` | Every image is optimised | Keep photos in `src/` | Readers download huge files |
| Forgetting `image.domains` | Remote images are always optimised | List the domain in the config | The image is shown at full size |
| Preloading every font weight | More preloads load faster | Preload only what is used first | Slower first paint |

</div>

## Check Yourself

## Exercises

## What to Remember

- Keep images in `src/` and import them. `public/` images are not optimised.
- `<Image />` needs `alt`. `<Picture />` offers several formats.
- Remote images need a size and an allowed domain.
- `layout` makes images responsive. `image()` adds images to collections.
- The Fonts API serves fonts from your own site, with smart fallbacks.

Next: **External Data and Environment Variables**, where Asha's site reads data from an API.
