At the beginning of this blog, everything seemed simple. I had one Layout.astro file. It handled SEO, the Header, the Footer, fonts, and even some scripts specific to the homepage. It was my “do-it-all Layout”.
But as I added a contact page, Pagefind search, and complex articles, this file became an unmanageable monolith. Every modification for an article risked breaking the homepage display.
Here is why (and how) I decided to shatter it all.
The “Swiss Army Knife” Layout Problem
A monolithic Layout suffers from three major flaws:
- Cognitive pollution: Too much conditional logic (
{isHomePage && <Hero />}). - Unnecessary weight: Loading search scripts on a contact page that doesn’t need them.
- Rigidity: Hard to change the structure of an article without impacting the rest of the site.
The Solution: The Onion Pattern 🧅
I deconstructed my architecture into specialized layers. Instead of a single block, I now use nested layouts.
1. The Base
This is the pure HTML skeleton. It only contains what is 100% common to the entire site:
- The
<!DOCTYPE html>and<html>tags. - The
<head>with global metadata and favicons. - The
<body>structure with the<Header />and<Footer />. - The
<ClientRouter />component for fluid transitions.
2. The SEO Layer
Rather than managing OpenGraph tags (for LinkedIn/Bluesky) directly in the layout, I created a dedicated component. It receives props and generates the tags dynamically.
3. Specialized Layouts
This is where the magic happens. I created “sub-layouts” that use the base components:
- Layout.astro: For simple static pages (Contact, About). It centers the content and handles standard margins.
- BlogPost.astro: Dedicated to blog posts. It handles the Table of Contents, the reading progress bar, and syntax highlighting scripts.
What does the code look like now?
Today, my src/layouts/Layout.astro file looks like this:
---
import Analytics from "@vercel/analytics/astro";
import BaseHead from "../components/BaseHead.astro"; // <-- The new conductor
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
const {
title = "Valentin Besse's Blog",
description = "Welcome to my tech blog. We talk about code, Astro, and legacy.",
image,
schema,
} = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<BaseHead
title={title}
description={description}
image={image}
schema={schema}
/>
</head>
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<Header />
<main id="main-content">
<slot />
</main>
<Footer />
<Analytics />
</body>
</html>
The Immediate Benefits
- Easier maintenance: If I want to modify my Footer, I know I have a specific dedicated component. If I want to change the design of my articles, it’s in BlogPost.
- Performance: I only load the progress bar script in the article layout.
- Clarity: My content pages have become extremely easy to read again.
Conclusion
If you are launching an Astro project, don’t fall into the single Layout trap. Separate responsibilities from the start. Think of your architecture as a set of LEGO pieces that you snap together according to the needs of each page.
Your future “you”, the one who will have to fix a bug in 6 months, will thank you.