Skip to content
Astro as layouts 4 min read

Layouts

Most pages on a site share the same skeleton: the same <html> and <head> boilerplate, the same navigation bar, the same footer. Copying that markup into every page is tedious and error-prone. In Astro, a layout is just a regular component you wrap your page content in, so that shared structure lives in exactly one place. Because layouts are plain Astro components, they ship zero JavaScript by default and render entirely on the server.

What a layout is

There is nothing special about a “layout” file from Astro’s point of view — it is an ordinary .astro component. The convention is to put these components in src/layouts/ and to have them render a complete or partial HTML document while exposing a <slot /> where the page’s unique content goes. The page that uses the layout imports it and renders its own content as children.

A typical layout owns three things every page needs:

  • The document shell: <html>, <head>, and <body> tags.
  • The <head> contents: charset, viewport, title, and meta tags.
  • The chrome: a shared header, navigation, and footer.

A minimal layout

Here is a complete base layout. It accepts a title prop, renders the head, wraps the page in a header and footer, and drops the page body into the <slot />.

---
// src/layouts/BaseLayout.astro
interface Props {
  title: string;
  description?: string;
}

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

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="description" content={description} />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <title>{title}</title>
  </head>
  <body>
    <header>
      <nav>
        <a href="/">Home</a>
        <a href="/blog">Blog</a>
        <a href="/about">About</a>
      </nav>
    </header>

    <main>
      <slot />
    </main>

    <footer>
      <p>&copy; {new Date().getFullYear()} DevCraftly</p>
    </footer>
  </body>
</html>

The <slot /> element marks where child content is injected — it is the layout equivalent of children in React or ng-content in Angular.

Using a layout in a page

A page imports the layout and renders its content between the layout’s tags. Props are passed exactly like any other component.

---
// src/pages/index.astro
import BaseLayout from "../layouts/BaseLayout.astro";
---

<BaseLayout title="Home" description="Welcome to my Astro site">
  <h1>Hello, world</h1>
  <p>This content is injected into the layout's slot.</p>
</BaseLayout>

Everything between <BaseLayout> and </BaseLayout> is rendered where the layout placed its <slot />. The generated page is a single static HTML document with no client-side JavaScript unless you explicitly opt in.

Tip: Layouts are also where you add an island when you need interactivity site-wide — for example a <ThemeToggle client:idle /> in the header. The directive keeps the JS scoped to that one component, preserving Astro’s zero-JS-by-default model for the rest of the page.

Layouts and Markdown

Layouts shine with Markdown and MDX pages. Set the layout key in a Markdown file’s frontmatter and Astro renders the compiled content into that layout automatically. The layout receives all frontmatter on the frontmatter prop.

---
layout: ../../layouts/PostLayout.astro
title: "My first post"
pubDate: "2026-06-14"
---

This Markdown body is rendered into the slot of PostLayout.
---
// src/layouts/PostLayout.astro
const { frontmatter } = Astro.props;
---

<article>
  <h1>{frontmatter.title}</h1>
  <time>{frontmatter.pubDate}</time>
  <slot />
</article>

For content collections, you typically render the entry’s <Content /> component inside a layout instead, which gives you full type safety over the frontmatter schema.

Props commonly passed to layouts

PropTypePurpose
titlestringDocument <title> and visible heading
descriptionstring<meta name="description"> for SEO
imagestringOpen Graph / social card image URL
frontmatterobjectAuto-passed by Markdown/MDX pages
classstringOptional modifier for the <body> or wrapper

Verifying the output

Run the dev server and the rendered HTML contains the layout’s shell wrapping your page content.

npm run dev

Output:

  astro  v5.0.0 ready in 312 ms
  ┃ Local    http://localhost:4321/
  ┃ Network  use --host to expose

Viewing source on the page shows the full document — head, header, your content, and footer — assembled from the single layout file.

Best practices

  • Keep one BaseLayout that owns the <html>/<head> shell, and compose more specific layouts on top of it rather than duplicating the document boilerplate.
  • Type your layout props with a Props interface so consumers get autocompletion and errors at build time.
  • Give every layout prop a sensible default (like description) so pages don’t have to pass everything.
  • Pass SEO-critical values (title, description, social image) as props instead of hardcoding them in the layout.
  • Add interactivity through islands with client:* directives in the layout only where needed, keeping the rest of the page zero-JS.
  • Store layouts in src/layouts/ so their intent is obvious and they are easy to find.
Last updated June 14, 2026
Was this helpful?