The blog system in this portfolio is built with Next.js App Router, featuring static site generation, markdown processing, and SEO optimization.
Architecture
The blog follows a file-based routing structure with markdown content:
src/
├── app/
│ ├── blog/
│ │ ├── page.tsx # Blog listing page
│ │ └── post/
│ │ └── [slug]/
│ │ └── page.tsx # Individual post page
│ └── components/
│ └── blog/
│ ├── BlogHeader.tsx
│ ├── BlogPostCard.tsx
│ ├── BlogPostContent.tsx
│ └── BlogPostList.tsx
├── content/
│ └── blog/
│ ├── openai-townhall.md
│ ├── intern-experience-aws.md
│ └── ...
└── lib/
└── blog.ts # Blog utilities
Key Components
Blog Listing (/blog)
The main blog page displays all posts in a grid layout:
File: src/app/blog/page.tsx:41
export default function BlogListingPage () {
const blogPosts = getSortedPostsData ();
return (
< div className = "min-h-screen text-white" >
< BlogHeader
title = "Writings"
subtitle = ""
backLink = "/"
backText = "Back to Portfolio"
/>
< BlogPostList posts = { blogPosts } />
< Footer />
</ div >
);
}
The blog listing includes structured data (JSON-LD) for SEO, helping search engines understand the blog content.
Individual Posts (/blog/post/[slug])
Dynamic routes are generated at build time using generateStaticParams:
File: src/app/blog/post/[slug]/page.tsx:177
export async function generateStaticParams () {
const slugs = getAllPostSlugs ();
return slugs ;
}
export default async function IndividualBlogPostPage ({
params ,
} : BlogPostPageProps ) {
const { slug } = await params ;
const post = await getPostData ( slug );
if ( ! post ) {
notFound ();
}
return (
< div className = "min-h-screen text-white" >
< BlogHeader title = "" backLink = "/blog" backText = "Back to Writings" />
< BlogPostContent post = { post } />
< Footer />
</ div >
);
}
Blog Utilities
The src/lib/blog.ts file provides core functionality:
Key Functions
getSortedPostsData()
Reads all markdown files from src/content/blog/, parses frontmatter with gray-matter, and returns posts sorted by date (latest first). File: src/lib/blog.ts:28
getPostData(slug)
Fetches a specific post by slug, processes markdown to HTML using remark, and applies custom Spotify-themed styling. File: src/lib/blog.ts:72
getAllPostSlugs()
Returns all available post slugs for static generation. File: src/lib/blog.ts:60
SEO Features
The blog system includes comprehensive SEO optimization:
Each post generates dynamic metadata including:
Title and description
Open Graph tags for social sharing
Twitter Card metadata
Canonical URLs
Structured data (JSON-LD) for BlogPosting schema
File: src/app/blog/post/[slug]/page.tsx:33
export async function generateMetadata ({
params ,
} : BlogPostPageProps ) : Promise < Metadata > {
const { slug } = await params ;
const post = await getPostData ( slug );
return {
title: ` ${ post . title } | ${ DEFAULT_AUTHOR } ` ,
description: excerpt ,
openGraph: {
type: "article" ,
url: canonicalUrl ,
title: post . title ,
images: [{ url: post . image }],
publishedTime: toIsoDate ( post . date ),
},
// ... more metadata
};
}
TypeScript Interfaces
Blog posts use well-defined TypeScript interfaces:
File: src/lib/blog.ts:9
export interface BlogPost {
id : string ;
title : string ;
description : string ;
date : string ;
readTime : string ;
image : string ;
slug : string ;
author ?: string ;
tags ?: string [];
category ?: string ;
excerpt ?: string ;
content ?: string ;
}
export interface BlogPostWithContent extends BlogPost {
content : string ;
}
Styling System
The blog uses a Spotify-inspired dark theme with custom CSS classes:
spotify-green - Primary accent color (#1DB954)
spotify-white - Text color with opacity variations
spotify-light-dark - Card backgrounds
spotify-dark - Main background
Custom styling is applied to markdown HTML in src/lib/blog.ts:106 using the applyCustomStyling() function, which transforms standard HTML elements into styled components.
Next Steps
Creating Posts Learn how to write and publish new blog posts
Markdown Support Explore supported markdown features and syntax