Hello! Welcome back to our journey building a modern accounting website.
In the last lesson, we successfully created dynamic page templates for individual blog posts and services using getStaticPaths. You might have noticed that the templates for blog/[slug].astro and services/[slug].astro both started by importing BaseLayout and setting up a similar structure. While this works, it leads to code duplication. If you wanted to add, for instance, an author byline to all blog posts but not service pages, you'd have to manage that logic within each template. This isn't scalable and violates the "Don't Repeat Yourself" (DRY) principle, which is crucial for the clean, maintainable architecture we're aiming for.
Today, we'll solve this by learning to implement nested layouts for different content types. You will create a specific layout for your blog posts that handles their unique structure (like displaying the author and date) and then nest it inside your main BaseLayout. This powerful pattern allows you to compose layouts, creating a clean separation between site-wide structure, content-type-specific structure, and the final page content.
This concept is similar to component composition in React, where you build complex UIs by nesting smaller, specialized components.

1. The Concept: A Slot Within a Slot
The magic of nested layouts in Astro is achieved by creating a "slot within a slot." Here’s how the hierarchy works:
BaseLayout.astro: This is your top-level layout. It contains the<html>,<head>,<body>tags, the site header, and the footer. It has a single<slot />where the entire page's content is injected.BlogPostLayout.astro(our new layout): This layout is specific to blog posts. It imports and usesBaseLayout. Its own content—such as a blog post title, author name, and publish date—is placed inside theBaseLayoutcomponent tags. It then provides its own<slot />for the actual Markdown/MDX content of the blog post.- Page Content (
[slug].astroor an MDX file): This is the final layer. Its content gets injected into the<slot />ofBlogPostLayout.astro.
The flow is: MDX Content → BlogPostLayout → BaseLayout.
The following short video provides a great practical demonstration of this exact pattern.
The video 'Markdown SO EASY in Astro!' by Coding in Public quickly demonstrates how to create a nested layout (which he calls a 'blog wrapper') that inherits from a base layout.
Please watch from 04:55 to 06:17. Pay close attention to how he creates blog wrapper.astro, imports BaseLayout inside it, and then uses a <slot /> to create the 'slot inside a slot' effect.
As you saw, a nested layout is just a regular Astro component that happens to use another layout component in its template. This simple yet powerful idea is key to building organized and scalable Astro sites.
2. Practical Implementation: Creating a BlogPostLayout
Let's apply this concept to our project. We will create a BlogPostLayout.astro that will be responsible for the common structure of every single blog post page.
Step 1: Create the Layout File
In your project, create a new file at src/layouts/BlogPostLayout.astro.
Step 2: Build the Nested Layout
Now, add the following code to your new BlogPostLayout.astro file. This code defines the structure for a blog post, pulling metadata from the post's frontmatter and passing the page title up to the BaseLayout.
// src/layouts/BlogPostLayout.astro
---
import BaseLayout from './BaseLayout.astro';
import FormattedDate from '../components/FormattedDate.astro'; // From our previous lessons
// The props for this layout will be passed from our [slug].astro page.
// We expect a `frontmatter` object with our post's metadata.
const { frontmatter } = Astro.props;
---
<BaseLayout title={frontmatter.title} description={frontmatter.description}>
<article class="post-layout">
<div class="post-header">
<h1>{frontmatter.title}</h1>
<div class="post-meta">
<span>By {frontmatter.author}</span>
<FormattedDate date={frontmatter.pubDate} />
</div>
</div>
{frontmatter.heroImage &&
<img src={frontmatter.heroImage} alt="" class="hero-image" />
}
<!-- The actual MDX content from the post will be injected here -->
<div class="prose">
<slot />
</div>
</article>
</BaseLayout>
<style>
.post-layout {
max-width: 80ch;
margin-inline: auto;
padding-inline: 1rem;
}
.post-header {
text-align: center;
margin-bottom: 2rem;
}
.post-header h1 {
font-size: 3rem;
margin-bottom: 0.5rem;
}
.post-meta {
display: flex;
justify-content: center;
gap: 1rem;
color: #666;
}
.hero-image {
width: 100%;
max-height: 400px;
object-fit: cover;
margin-bottom: 2rem;
border-radius: 8px;
}
.prose {
max-width: 65ch;
margin: auto;
line-height: 1.7;
}
</style>
Code Breakdown:
import BaseLayout: We import the parent layout we want to nest our component within.const { frontmatter } = Astro.props;: This layout expects to receive thefrontmatterdata from the MDX file.<BaseLayout title={frontmatter.title} ...>: We use theBaseLayoutcomponent and pass the post's title and description up as props. This ensures the browser tab and meta tags are set correctly for each post.<h1>{frontmatter.title}</h1>: We render post-specific elements, like the title, author, and date.<slot />: This is the crucial part. The rendered HTML content of the individual MDX blog post file will be placed here.
The Astro documentation provides a clear, step-by-step guide for this process, which is excellent for reinforcing what we've just done.
Combine layouts to get the best of both worlds - Astro Docs
The official Astro documentation page titled 'Combine layouts to get the best of both worlds' walks you through this exact process. It's a great reference to solidify your understanding.
Read through the section 'Nest your two layouts'. Notice how it guides you to import the base layout, wrap the content, pass up the pageTitle prop, and remove duplicated elements. This perfectly mirrors the steps we are taking.
3. Updating the Dynamic Page Template
Now that our specialized BlogPostLayout is ready, we can simplify our dynamic page template at src/pages/blog/[slug].astro. It no longer needs to handle the presentational details of a blog post; it just needs to fetch the data and pass it to the correct layout.
Open src/pages/blog/[slug].astro and replace its content with the following:
// src/pages/blog/[slug].astro
---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPostLayout from '../../layouts/BlogPostLayout.astro'; // Use the new layout
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: post, // Pass the entire post object as props
}));
}
const post = Astro.props;
const { Content } = await post.render();
---
<BlogPostLayout frontmatter={post.data}>
<Content />
</BlogPostLayout>
Look how much cleaner that is!
- Before: The template contained a mix of data-fetching logic (
getStaticPaths) and presentation markup (h1, dates, etc.). - After: The page template is now lean. Its only jobs are to fetch the correct post and render it within the
BlogPostLayout, passing the post's frontmatter (post.data) to the layout as a prop. The<Content />component is passed into the defaultslotof theBlogPostLayout.
This separation of concerns is a hallmark of a well-structured project and is exactly the kind of clean architecture that makes a site easy to maintain and scale.
Test your understanding!
Following the pattern we just established for blog posts, imagine you need to create a specific layout for your service pages called ServicePageLayout.astro. This layout should also inherit from BaseLayout.
Unlike a blog post, a service page might not have an author or publication date. Instead, it might have a prominent "Request a Consultation" button below the content.
How would you structure src/layouts/ServicePageLayout.astro? What would you keep from the BlogPostLayout example, and what would you change?
Show answer
Your ServicePageLayout.astro would look conceptually similar but tailored for services.
- Inheritance: It would still import and wrap its content in
<BaseLayout>, passing up props liketitle. - Props: It would still expect
frontmatterfromAstro.props. - Content-Specific Elements: Instead of
authorandpubDate, you would display the service'stitleand maybe asummaryfrom the frontmatter. - Unique Elements: After the
<slot />where the service description goes, you would add your new element, like a "Request a Consultation" button.
Here's a possible implementation:
// src/layouts/ServicePageLayout.astro
---
import BaseLayout from './BaseLayout.astro';
// Let's assume you have a reusable Button component
import Button from '../components/ui/Button.astro';
const { frontmatter } = Astro.props;
---
<BaseLayout title={frontmatter.title} description={frontmatter.summary}>
<section class="service-layout">
<h1>{frontmatter.title}</h1>
<p class="summary">{frontmatter.summary}</p>
<hr />
<div class="prose">
<slot />
</div>
<div class="cta-section">
<h3>Ready to get started?</h3>
<Button href="/contact">Request a Consultation</Button>
</div>
</section>
</BaseLayout>
<style>
/* Styles specific to a service page */
.service-layout {
max-width: 80ch;
margin-inline: auto;
padding-inline: 1rem;
}
.summary {
font-size: 1.2rem;
color: #555;
text-align: center;
margin-block: 1rem 2rem;
}
.cta-section {
text-align: center;
margin-top: 3rem;
padding: 2rem;
background-color: #f9f9f9;
border-radius: 8px;
}
</style>
This demonstrates how you can create distinct, reusable, and maintainable structures for every different type of content on your site.
Conclusion
Fantastic work! You've successfully implemented one of the most important structural patterns in Astro for building content-rich websites. By nesting layouts, you've made your project significantly more organized, maintainable, and scalable.
Key Takeaways:
- Nested layouts allow you to create specialized templates for different content types (like blogs or services) while reusing a common site-wide structure (
BaseLayout). - This pattern follows the DRY principle by centralizing the layout logic for each content type.
- A nested layout is just an Astro component that uses another layout and provides its own
<slot />. - You can pass props from a child layout (e.g.,
BlogPostLayout) to a parent layout (BaseLayout) to control things like the page title. - This approach cleanly separates data-fetching logic (in
[slug].astro) from presentation logic (in the layouts).
Up Next:
So far, our site is entirely static, which is fantastic for performance. However, modern websites often need client-side interactivity—think of dropdown menus, contact forms, or live-filtering data.
In the next module, "Integrating React and Client-Side Interactivity," we'll begin to bridge this gap. We will start by setting up the React integration in our Astro project and learn how to render our first interactive React component within an otherwise static Astro page. This will tap directly into your existing React skills and introduce you to Astro's innovative "islands architecture."