# Layouts and the Document Head

Course: [Astro](https://www.devtutorials.ai/learn/astro) · Section: Build Pages
Source: https://www.devtutorials.ai/learn/astro/build-pages/layouts

> Layout components with slot, the html, head and body elements, a shared header and footer, passing props to a layout, nested layouts, page titles, meta descriptions, Open Graph tags, canonical URLs, favicons and a reusable head component.

## Layouts

- Every page of a site shares parts: the `<head>`, the header, the menu, the footer.
- Copying them into every page is slow, and one change means editing every file.
- A **layout** is a component that holds these shared parts and a `<slot />` for the page's own content.
- In this lesson, you will build layouts and fill the document head with the right tags.

**Simple lesson:** write the page frame once, in a layout. Each page fills in the middle.

### A Real-World Example

- Asha's site now has a home page and an about page.
- Both need the same `<head>`, header and footer.
- When she adds a "Blog" link to the menu, it should appear on every page at once.
- She moves the shared parts into `BaseLayout.astro`. Each page only writes its own content.

Press **▶ Run**. Then click **About**. The header and footer stay the same. Only the middle changes.

    <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width" />
      <title>{title} | Asha's Dev Journal</title>
    </head>
    <body>
      <header>
        <strong>Asha's Dev Journal</strong>
        <nav>
          <a href="/">Home</a>
          <a href="/about">About</a>
        </nav>
      </header>
      <main>
        <slot />
      </main>
      <footer>© 2026 Asha</footer>
    </body>
</html>
`,
    "src/pages/index.astro": `---
---

    <h1>Welcome</h1>
    <p>Notes from my coding journey.</p>
`,
    "src/pages/about.astro": `---
---

    <h1>About Me</h1>
    <p>I am a student from Chennai.</p>
`,
  }}
/>

What happened:

- Each page wraps its content in `<BaseLayout>`.
- The content lands where the layout has `<slot />`.
- `title` is a prop. The layout puts it into the `<title>` tag.

### What You Will Learn

In this lesson, you will learn:

1. What a layout is and where it lives.
2. How a layout uses `<slot />` and props.
3. Why a layout holds `<html>`, `<head>` and `<body>`.
4. How to nest one layout inside another.
5. Which tags belong in the document head.
6. How to build Open Graph tags and canonical URLs.
7. How to make a reusable head component.

## Layout Basics

### What Layouts Are

- **Layout** = an Astro component that gives pages a shared structure.
- It is a normal component. The name "layout" only tells you its job.
- Put layouts in `src/layouts`. Name them after their use: `BaseLayout.astro`, `BlogPostLayout.astro`.

### Using slot in a Layout

- The layout has one `<slot />`, usually inside `<main>`.
- The page's content fills it.
- This is the default slot from **Slots and Composition**, used for a whole page.

<figure className="ast-fig ngc-fig">
<svg viewBox="0 0 640 220" role="img" aria-labelledby="fig-layout" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-layout">Animation: the layout frame with header and footer stays still, while three different page contents slide into its middle slot one after another.</title>
  <rect x="200" y="14" width="240" height="196" rx="10" style={{ fill: "var(--ngc-win)", stroke: "var(--ngc-slate)", strokeWidth: 1.5 }} />
  <rect x="212" y="26" width="216" height="32" rx="6" style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)" }} />
  <text x="320" y="47" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>Header (layout)</text>
  <rect x="212" y="166" width="216" height="32" rx="6" style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)" }} />
  <text x="320" y="187" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>Footer (layout)</text>
  <rect x="212" y="68" width="216" height="88" rx="6" style={{ fill: "none", stroke: "var(--ngc-slate)", strokeDasharray: "5 5" }} />
  <g style={{ fontFamily: "var(--sans)", fontSize: 14, fontWeight: 700 }}>
    <g><animate attributeName="opacity" values="1;1;0;0;1" keyTimes="0;0.3;0.33;0.97;1" dur="6s" repeatCount="indefinite" /><rect x="220" y="76" width="200" height="72" rx="6" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)" }} /><text x="320" y="117" textAnchor="middle" style={{ fill: "var(--ngc-green)" }}>Home content</text></g>
    <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.33;0.36;0.63;0.66;1" dur="6s" repeatCount="indefinite" /><rect x="220" y="76" width="200" height="72" rx="6" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)" }} /><text x="320" y="117" textAnchor="middle" style={{ fill: "var(--ngc-blue)" }}>About content</text></g>
    <g opacity="0"><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.66;0.69;0.97;1" dur="6s" repeatCount="indefinite" /><rect x="220" y="76" width="200" height="72" rx="6" style={{ fill: "var(--ngc-orange-soft)", stroke: "var(--ngc-orange)" }} /><text x="320" y="117" textAnchor="middle" style={{ fill: "var(--ngc-orange)" }}>Blog post content</text></g>
  </g>
  <text x="100" y="117" textAnchor="middle" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--sans)", fontSize: 13 }}>Written once</text>
  <text x="540" y="117" textAnchor="middle" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--sans)", fontSize: 13 }}>Changes per page</text>
</svg>
<figcaption>The frame stays. The slot in the middle shows each page's content.</figcaption>
</figure>

### Applying a Layout to a Page

1. Import the layout in the page's script.
2. Wrap the page's content in the layout's tag.
3. Pass props, such as `title`, as attributes.

**Simple lesson:** a layout is a component with the page frame and one `<slot />`.

## Page Structure in Layouts

### The html, head and body Elements

- A full HTML page needs `<html>`, `<head>` and `<body>`.
- Put them in the layout, not in each page. Then no page can forget them.
- `<html lang="en">` tells browsers and screen readers the page's language.
- `<meta charset="utf-8" />` makes sure letters like "₹" and "é" show correctly.
- `<meta name="viewport" content="width=device-width" />` makes the page fit phone screens.

### Shared Header and Footer

- Write the header and footer once in the layout, or as components the layout imports.
- A change to the menu now shows on every page.
- The header can mark the current page, using `Astro.url.pathname`.

Press **▶ Run**, then click **Blog**. The menu marks the page you are on.

    <strong>Dev Journal</strong>
    <nav>
      {links.map((link) => (
        <a href={link.href} aria-current={path === link.href ? "page" : undefined}>
          {path === link.href ? "▸ " : ""}{link.label}
        </a>
      ))}
    </nav>
</header>
`,
    "src/components/Footer.astro": `<footer>© 2026 Asha · Built with Astro</footer>
`,
    "src/layouts/BaseLayout.astro": `---

const { title } = Astro.props;
---

<html lang="en">
    <head>
      <meta charset="utf-8" />
      <title>{title}</title>
    </head>
    <body>
      <main><slot /></main>
    </body>
</html>
`,
    "src/pages/index.astro": `---
---

    <h1>Welcome</h1>
`,
    "src/pages/blog.astro": `---
---

    <h1>All posts</h1>
`,
  }}
/>

- `aria-current="page"` tells screen readers which link is the current page.
- When the value is `undefined`, Astro leaves the attribute out.

### Passing Props to a Layout

- A layout reads props like any component: `const { title, description } = Astro.props;`.
- Use them in the head, so each page gets its own title and description.

Press **▶ Run**. Each page sends a different title and description to the same layout. Switch pages and read **HTML sent to browser**.

    <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width" />
      <title>{title} | Asha's Dev Journal</title>
      <meta name="description" content={description} />
    </head>
    <body>
      <slot />
    </body>
</html>
`,
    "src/pages/index.astro": `---
---

    <h1>Welcome</h1>
    <a href="/projects">My projects</a>
`,
    "src/pages/projects.astro": `---
---

    <h1>My projects</h1>
    <a href="/">Home</a>
`,
  }}
/>

- The home page sends no `description`, so the layout's default is used.
- The projects page sends its own.

**Simple lesson:** the layout owns the page structure. Pages pass in only what is different.

## Nested Layouts

### Placing a Layout Inside a Layout

- A layout can use another layout.
- `BlogPostLayout` can wrap the post in a date and author line, then pass everything to `BaseLayout`.
- The page uses only `BlogPostLayout`. It gets the base frame for free.

### When to Nest Layouts

- Nest when a group of pages adds something on top of the base frame.
- Examples: blog posts add a date and author. Docs pages add a sidebar.
- Do not nest just to split a file. Two or three levels are plenty.

Only the outermost layout should write `<html>`, `<head>` and `<body>`. If a nested layout writes them again, the page gets two of each.

    <head>
      <meta charset="utf-8" />
      <title>{title} | Asha's Dev Journal</title>
    </head>
    <body>
      <header><strong>Asha's Dev Journal</strong></header>
      <main><slot /></main>
      <footer>© 2026 Asha</footer>
    </body>
</html>
`,
    "src/layouts/BlogPostLayout.astro": `---

const { title, date, author = "Asha" } = Astro.props;
---

    <article>
      <h1>{title}</h1>
      <p class="byline">By {author} · {date}</p>
      <slot />
    </article>
`,
    "src/pages/index.astro": `---
---

    <p>I wrote my header once. Now every page has it.</p>
    <p>My blog posts also get a date line, from a second layout.</p>
`,
  }}
/>

- The page gives only the title, the date and its paragraphs.
- `BlogPostLayout` adds the heading and the byline.
- `BaseLayout` adds the `<head>`, header and footer.

**Simple lesson:** nest a layout when a group of pages adds its own frame on top of the base frame.

## Managing the Document Head

- **Document head** = the `<head>` part of a page. Visitors do not see it, but browsers, search engines and social apps read it.

### Title and Meta Description

- `<title>` is the text on the browser tab and the blue link in search results.
- Keep it under about 60 characters. Put the most important words first.
- `<meta name="description">` is the short summary search engines may show under the title.
- Keep it to one or two sentences, about 150 characters.

### Open Graph and Social Tags

- **Open Graph tags** = `<meta property="og:...">` tags that social apps read.
- When someone shares your link on WhatsApp, LinkedIn or X, the app shows a card from them.
- The main ones: `og:title`, `og:description`, `og:image` and `og:url`.
- `og:image` must be a full URL, such as `https://asha.dev/social.png`.

### Canonical URLs

- **Canonical URL** = the one official address of a page.
- The same page can sometimes be reached at two addresses, such as with and without `?ref=share`.
- `<link rel="canonical" href="...">` tells search engines which address to list.
- Build it from two values:
  - `Astro.url.pathname` = the path of the current page, such as `/about`.
  - `Astro.site` = the `site` from `astro.config.mjs`.
- `new URL(Astro.url.pathname, Astro.site)` joins them: `https://asha.dev/about`.

### Favicons

- **Favicon** = the small icon on the browser tab.
- Put the file in `public/`, then link it: `<link rel="icon" type="image/svg+xml" href="/favicon.svg" />`.

### Reusable Head Components

- Put all the head tags in one component, such as `BaseHead.astro`.
- The layout uses `<BaseHead title={title} description={description} />` inside `<head>`.
- Every page now gets correct, complete head tags.

Press **▶ Run**. Read **HTML sent to browser**. Then open the about page from the address bar and compare the canonical link.

<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />

<meta property="og:type" content="website" />
<meta property="og:url" content={canonical} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={imageUrl} />
`,
    "src/layouts/BaseLayout.astro": `---

const { title, description } = Astro.props;
---

<html lang="en">
    <head>
    </head>
    <body>
      <slot />
    </body>
</html>
`,
    "src/pages/index.astro": `---
---

    <h1>Welcome</h1>
    <a href="/about">About</a>
`,
    "src/pages/about.astro": `---
---

    <h1>About Me</h1>
    <a href="/">Home</a>
`,
  }}
/>

- `og:image` became a full URL, because social apps need one.
- The canonical link changes from page to page, because `Astro.url.pathname` changes.

Set `site` in `astro.config.mjs` before you build canonical or Open Graph URLs. Without it, `Astro.site` is `undefined` and the links lose their domain.

On your computer, `npm run dev` shows `localhost:4321` in `Astro.url`, but `Astro.site` always holds your real domain. That is why canonical links are built from `Astro.site`.

**Simple lesson:** a reusable head gives every page a title, a description, social tags and a canonical link.

## TL;DR

1. **Layout** = a component with the page frame and a `<slot />`. Lives in `src/layouts`.
2. **Use a layout** = import it, wrap the content, pass props.
3. **html, head, body** = in the layout, so no page forgets them.
4. **Nested layout** = a layout that wraps another layout, such as `BlogPostLayout` around `BaseLayout`.
5. **title** = tab text and search link. **meta description** = search summary.
6. **Open Graph** = `og:title`, `og:description`, `og:image`, `og:url` for social cards.
7. **Canonical URL** = `new URL(Astro.url.pathname, Astro.site)`.
8. **Favicon** = file in `public/`, linked with `<link rel="icon">`.
9. **BaseHead** = one component for all head tags.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| No `<slot />` in the layout | The page content appears anyway | Add `<slot />` where content goes | Every page shows only the header and footer |
| Copying `<head>` into every page | Each page needs its own copy | Put it in the layout or `BaseHead` | Pages drift apart and some miss tags |
| The same title on every page | One title is enough | Pass a `title` prop per page | Search results all look the same |
| Relative `og:image`, like `/social.png` | Social apps know your domain | Use a full URL from `Astro.site` | Shared links show no picture |
| No `site` in the config | Astro guesses the domain | Set `site` | Canonical and social links break |
| No `lang` on `<html>` | It does not matter | Write `<html lang="en">` | Screen readers may read the page in the wrong voice |

</div>

## Check Yourself

## Exercises

## What to Remember

- A layout is a component with the page frame and a `<slot />`.
- Put `<html>`, `<head>` and `<body>` in the layout.
- Pass each page's title and description as props.
- Nest layouts when a group of pages needs an extra frame.
- A reusable head gives every page its title, description, social tags and canonical link.

Next: **Routing**, where you learn every way a file becomes an address.
