Skip to main content

Overview

Astro Portfolio v3 includes two main layout components that provide consistent structure and SEO optimization across your site. Layouts wrap your page content with common elements like navigation, footer, and metadata.

Available Layouts

The base layout used for all standard pages including the homepage, about page, and other static pages.Location: src/layouts/Layout.astro

Features

  • Complete SEO metadata (Open Graph, Twitter Cards, JSON-LD)
  • Favicon and manifest configuration
  • Theme initialization
  • Google Tag Manager integration
  • Astro view transitions
  • Persistent footer and social links

Base Layout (Layout.astro)

Props Interface

interface Props {
  title?: string;
  description?: string;
  ogTitle?: string;
  ogDescription?: string;
  image?: string;
  author?: string;
  publishedTime?: string;
  type?: 'website' | 'article';
  canonical?: string;
}

Props Reference

title
string
The page title shown in browser tabs and search results
description
string
default:"siteConfig.description"
Meta description for SEO
ogTitle
string
default:"siteConfig.title"
Open Graph title for social media sharing
ogDescription
string
Open Graph description for social media previews
image
string
default:"'/opengraph.png'"
Social media preview image URL
author
string
default:"'Lewis Kori'"
Content author name
publishedTime
string
ISO 8601 timestamp for article publication date
type
'website' | 'article'
default:"'website'"
Open Graph content type
canonical
string
default:"Astro.url"
Canonical URL for the page

Usage Example

src/pages/about.astro
---
import Layout from '@/layouts/Layout.astro';
---

<Layout
  title="About | My Portfolio"
  description="Learn more about my journey and experience"
  type="website"
>
  <div class="container-fluid py-20">
    <h1>About Me</h1>
    <p>Your content here...</p>
  </div>
</Layout>

Layout Structure

src/layouts/Layout.astro
<html>
  <head>
    <!-- SEO Meta Tags -->
    <meta name="description" content={description} />
    
    <!-- Open Graph Tags -->
    <meta property="og:title" content={openGraphTitle} />
    
    <!-- Structured Data -->
    <script type="application/ld+json">
      <!-- JSON-LD for rich search results -->
    </script>
    
    <!-- Theme Initialization -->
    <script is:inline>
      <!-- Prevents flash of unstyled content -->
    </script>
  </head>
  
  <body>
    <BackgroundArt />
    <Navbar />
    <main class="min-h-screen">
      <slot />
    </main>
    <Footer />
    <StickySocials />
  </body>
</html>

Blog Layout (BlogLayout.astro)

Props Interface

interface Props {
  title: string;
  description: string;
  author: string;
  dateCreated: Date;
  tags?: string[];
  coverImage?: string;
  ogImage?: string;
  series?: string;
  readingTime: number;
  sponsors?: CollectionEntry<'sponsors'>[];
  relatedBlogs?: CollectionEntry<'blog'>[];
  canonical?: string;
}

Props Reference

title
string
required
The blog post title
description
string
required
Blog post description/excerpt
author
string
required
Author name
dateCreated
Date
required
Publication date as a Date object
tags
string[]
Array of tags for the post
coverImage
string
Cover image URL for the post
ogImage
string
Custom Open Graph image (falls back to coverImage)
series
string
Name of the blog series this post belongs to
readingTime
number
required
Estimated reading time in minutes
sponsors
CollectionEntry<'sponsors'>[]
Array of sponsor entries to display in sidebar
Related blog posts to show at the end
canonical
string
Canonical URL for cross-posted content

Usage Example

src/pages/blog/[slug].astro
---
import BlogLayout from '@/layouts/BlogLayout.astro';
import { getEntry } from 'astro:content';

const { slug } = Astro.params;
const post = await getEntry('blog', slug);
const { Content } = await post.render();
---

<BlogLayout
  title={post.data.title}
  description={post.data.description}
  author={post.data.author}
  dateCreated={post.data.dateCreated}
  tags={post.data.tags}
  coverImage={post.data.cover_image}
  readingTime={post.data.readingTime}
  relatedBlogs={relatedPosts}
>
  <Content />
</BlogLayout>

Layout Structure

<Layout type="article" {/* ...other props */}>
  <article class="py-16">
    <!-- Back Button -->
    <button id="back-button">Back to all posts</button>
    
    <!-- Header Section -->
    <header>
      <h1>{title}</h1>
      <div>Posted {formattedDate} • By {author}</div>
      {series && <a href={`/blog?series=${seriesSlug}`}>Series: {series}</a>}
      {coverImage && <Image src={coverImage} />}
      <ShareModal />
    </header>
    
    <!-- Main Content Grid -->
    <div class="grid lg:grid-cols-5">
      <!-- Blog Content -->
      <div class="lg:col-span-3">
        <slot />
      </div>
      
      <!-- Sponsors Sidebar -->
      {hasSponsors && <SponsorsSidebar />}
    </div>
    
    <!-- Tags -->
    <div class="tags">...</div>
    
    <!-- Social Share -->
    <SocialShare />
    
    <!-- Related Posts -->
    {relatedBlogs.length > 0 && <section>...</section>}
  </article>
  
  <NewsletterModal />
</Layout>

Creating Custom Layouts

You can create specialized layouts for different content types:
1

Create layout file

Create a new file in src/layouts/:
touch src/layouts/ProjectLayout.astro
2

Import base layout

Extend the base layout for consistency:
src/layouts/ProjectLayout.astro
---
import Layout from './Layout.astro';

interface Props {
  title: string;
  description: string;
  projectUrl?: string;
  githubUrl?: string;
  technologies: string[];
}

const { 
  title, 
  description, 
  projectUrl, 
  githubUrl, 
  technologies 
} = Astro.props;
---

<Layout
  title={`${title} | Projects`}
  description={description}
  type="website"
>
  <article class="container-fluid py-20">
    <header class="mb-12">
      <h1 class="text-4xl font-bold mb-4">{title}</h1>
      <p class="text-xl text-muted-foreground">{description}</p>
      
      <div class="flex gap-4 mt-6">
        {projectUrl && (
          <a href={projectUrl} class="btn-primary">
            View Project
          </a>
        )}
        {githubUrl && (
          <a href={githubUrl} class="btn-secondary">
            View Code
          </a>
        )}
      </div>
      
      <div class="flex flex-wrap gap-2 mt-6">
        {technologies.map(tech => (
          <span class="badge">{tech}</span>
        ))}
      </div>
    </header>
    
    <div class="prose max-w-none">
      <slot />
    </div>
  </article>
</Layout>
3

Use your layout

src/pages/projects/my-app.astro
---
import ProjectLayout from '@/layouts/ProjectLayout.astro';
---

<ProjectLayout
  title="My Awesome App"
  description="A revolutionary app that does amazing things"
  projectUrl="https://myapp.com"
  githubUrl="https://github.com/user/myapp"
  technologies={['React', 'TypeScript', 'Tailwind CSS']}
>
  <h2>About the Project</h2>
  <p>Project details here...</p>
</ProjectLayout>

Customizing Existing Layouts

Modify Navigation

Add custom navigation items to all pages:
src/layouts/Layout.astro
<Navbar />

<!-- Add a banner -->
<div class="bg-primary text-primary-foreground p-4 text-center">
  <p>🎉 New blog post available! <a href="/blog/latest">Read now</a></p>
</div>

<main>
  <slot />
</main>

Add Analytics

Integrate additional analytics beyond GTM:
src/layouts/Layout.astro
<head>
  <!-- Existing head content -->
  
  <!-- Plausible Analytics -->
  <script defer data-domain="yourdomain.com" 
    src="https://plausible.io/js/script.js">
  </script>
</head>

Customize Blog Content Styling

Modify the blog content prose styles:
src/layouts/BlogLayout.astro
<div class:list={[
  'prose prose-lg',
  'prose-headings:font-bold prose-headings:text-foreground',
  'prose-p:text-muted-foreground',
  'prose-a:text-secondary prose-a:no-underline hover:prose-a:underline',
  'prose-code:bg-muted prose-code:px-1.5 prose-code:py-0.5',
  'max-w-none'
]}>
  <slot />
</div>

Layout Best Practices

Consistent metadata: Always provide title and description props for better SEO.
Canonical URLs: Set canonical URLs for pages with duplicate content or cross-posted articles.
Image optimization: Use optimized images for Open Graph previews (1200x630px recommended).
Be careful when modifying the theme initialization script in Layout.astro - it prevents flash of unstyled content and must run synchronously before page render.

View Transitions

Both layouts include Astro’s view transitions for smooth page navigation:
import { ClientRouter, fade } from 'astro:transitions';

<ClientRouter />

<main transition:animate={fade({ duration: '0.5s' })}>
  <slot />
</main>
You can customize transition animations:
<!-- Custom slide transition -->
<main transition:animate="slide">
  <slot />
</main>

<!-- Persist elements across transitions -->
<div transition:persist="my-element">
  <Widget />
</div>

Structured Data

The base layout includes JSON-LD structured data for rich search results:
src/layouts/Layout.astro:100-170
{
  '@context': 'https://schema.org',
  '@graph': [
    {
      '@type': 'WebSite',
      url: siteUrl,
      name: siteName,
      description: defaultDescription,
    },
    {
      '@type': 'Person',
      name: 'Lewis Kori',
      jobTitle: siteConfig.tagline,
      // ... more fields
    }
  ]
}
You can extend this with additional schema types for different content.

Next Steps

Customize Components

Learn how to modify individual components

Styling Guide

Customize colors and themes

Site Config

Update your site configuration

Content Collections

Manage your blog posts and content

Build docs developers (and LLMs) love