Building composable layouts in Astro
By Flavio Copes
Learn how to build composable layouts in Astro, extracting shared HTML like the header and footer into a src/layouts file that wraps pages with a slot.
When a site grows past one page, shared HTML starts to duplicate. Every page repeats the doctype, the <head>, the navigation, the footer. Then you change something in the header and you are editing five files, hoping you caught them all.
A layout fixes this. A layout is an Astro component that owns the shared page structure while a route supplies the unique content. In other tools, like Hugo, you would use partials. In Astro it is all components, and by convention layouts live in src/layouts/.
Here is src/layouts/Layout.astro:
---
interface Props {
title: string
description: string
}
const { title, description } = Astro.props
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content={description} />
<title>{title}</title>
</head>
<body>
<nav><a href="/">Home</a></nav>
<main><slot /></main>
<footer>Built with Astro</footer>
</body>
</html>
The <slot /> element marks where page content will render. A page imports the layout and uses it like any other component:
---
import Layout from "../layouts/Layout.astro"
---
<Layout title="About" description="Learn who built this site">
<h1>About</h1>
<p>This content fills the layout's slot.</p>
</Layout>
Everything between <Layout> and </Layout> replaces the slot. The props fill the metadata.
The split of responsibilities is the useful part. The page remains responsible for accurate metadata: it knows its own title and description. The layout remains responsible for emitting a valid, consistent document: one place owns the doctype, the language attribute, and the navigation.
Layouts compose, because they are components. A blog can create a PostLayout that imports the base Layout, adds the post header inside it, and passes title and description through. Pages then wrap themselves in the most specific layout they need.
Like other Astro components, the layout runs while rendering. It does not stay alive in the browser, so it adds no client-side JavaScript to the page.
A classic mistake is forgetting <slot />. The layout renders fine, and every page using it comes out with an empty <main>. If page content silently disappears inside a correct shell, check the layout for a missing slot before debugging the page.
View two generated pages and verify that their shell is shared but their title, description, and main content differ.
Related posts about astro: