Skip to content

Layouts

In Next.js App Router you create a layout.tsx next to your pages and React automatically wraps children through it. In Astro there is no automatic layout wrapping — you create a layout component in src/layouts/ and explicitly import and use it inside each page. The layout receives page content through <slot />, which is Astro’s equivalent of React’s children prop.

Layouts require a multi-file project to observe — run these examples in a full Astro project (npm create astro@latest) locally.

Next.js
// app/layout.tsx (Next.js App Router)
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head>
<title>My Site</title>
</head>
<body>
<nav>My Nav</nav>
<main>{children}</main>
<footer>My Footer</footer>
</body>
</html>
);
}
Astro
---
// src/layouts/Layout.astro
interface Props {
title?: string;
}
const { title = 'My Site' } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{title}</title>
</head>
<body>
<nav>My Nav</nav>
<main>
<slot /> <!-- page content goes here -->
</main>
<footer>My Footer</footer>
</body>
</html>
Next.js
// app/about/page.tsx (Next.js App Router)
// layout.tsx wraps this automatically — nothing to import
export default function AboutPage() {
return <h1>About</h1>;
}
// -----------------------------------------
// pages/_app.tsx (Next.js Pages Router)
// import type { AppProps } from 'next/app';
// export default function App({ Component, pageProps }: AppProps) {
// return <Layout><Component {...pageProps} /></Layout>;
// }
Astro
---
// src/pages/about.astro
// Layouts must be explicitly imported in every page
import Layout from '../layouts/Layout.astro';
---
<Layout title="About">
<h1>About</h1>
<p>Welcome to the about page.</p>
</Layout>

Astro supports named slots for injecting content into specific regions of a layout — similar to named children patterns in React (using render props or portals) but built into the language.

React
// React named-area pattern (render props)
function Layout({ nav, main }: {
nav: React.ReactNode;
main: React.ReactNode;
}) {
return (
<div>
<aside>{nav}</aside>
<main>{main}</main>
</div>
);
}
// Usage
<Layout
nav={<NavLinks />}
main={<ArticleContent />}
/>
Astro
---
// src/layouts/TwoColumn.astro
---
<div>
<aside>
<slot name="sidebar" /> <!-- named slot -->
</aside>
<main>
<slot /> <!-- default slot -->
</main>
</div>
<!-- Usage in a page -->
---
import TwoColumn from '../layouts/TwoColumn.astro';
---
<TwoColumn>
<nav slot="sidebar">
<a href="/a">Link A</a>
</nav>
<article>Main content here</article>
</TwoColumn>
flowchart TD
  src["src/"] --> layouts["layouts/"]
  src --> pages["pages/"]
  layouts --> layout["Layout.astro (base HTML shell: head, nav, footer)"]
  layouts --> blogLayout["BlogLayout.astro (extends Layout, adds sidebar)"]
  pages --> index["index.astro imports Layout from '../layouts/Layout.astro'"]
  pages --> about["about.astro imports Layout from '../layouts/Layout.astro'"]
  pages --> blog["blog/"]
  blog --> slug["[slug].astro imports BlogLayout from '../../layouts/BlogLayout.astro'"]
Layouts and pages file tree
What Astro element is equivalent to React's `children` prop inside a layout?
How does Astro apply a layout to a page — compared to Next.js App Router?
Where is the conventional location for layout components in an Astro project?