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
Layout.astro
BlogLayout.astro
The base layout used for all standard pages including the homepage, about page, and other static pages. Location: src/layouts/Layout.astroFeatures
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
A specialized layout for blog posts with additional features for article content. Location: src/layouts/BlogLayout.astroFeatures
All base layout features
Author sidebar
Sponsors sidebar
Social sharing buttons
Related posts section
Newsletter modal
Blog-specific styling
Back navigation button
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
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
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
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
---
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
< 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
Blog post description/excerpt
Publication date as a Date object
Array of tags for the post
Cover image URL for the post
Custom Open Graph image (falls back to coverImage)
Name of the blog series this post belongs to
Estimated reading time in minutes
Array of sponsor entries to display in sidebar
relatedBlogs
CollectionEntry<'blog'>[]
Related blog posts to show at the end
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:
Create layout file
Create a new file in src/layouts/: touch src/layouts/ProjectLayout.astro
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 >
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:
< 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:
< 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