I developed a statically generated, performance-focused portfolio website with integrated blog functionality using Next.js 15, Tailwind CSS, and ContentLayer for content management.
- Responsive design with a dark theme
- A structured project showcase with detailed documentation pages
- A learning curve section to document my journey and learnings
- A full-featured blog system supporting series, tags, and technical content MDX integration for rich, interactive documentation with code highlighting
Technologies Used
Core Technologies
- Next.js 15: I chose the latest version of Next.js for its server components, improved routing system, and image optimization capabilities. The App Router provides a clean, file-system-based approach to organizing pages.
- React 19: Using React's latest version allowed me to leverage improved rendering performance and hooks for state management.
- TypeScript: For type safety across the codebase, reducing bugs and improving developer experience with better autocomplete and error detection.
- Tailwind CSS 4: For rapid UI development with utility classes that allow consistent styling while maintaining full design flexibility.
Content Management
- ContentLayer2: I selected ContentLayer for type-safe MDX content management, which creates - TypeScript types from my content schema. This gives me the best of both worlds: file-based content that's fully typed.
- MDX: For writing blog posts and project documentation with JSX components embedded in Markdown, allowing for rich interactive content.
Performance & UI Enhancements
- Sharp: For optimized image processing, generating responsive images and blur placeholders for improved loading experience.
- Framer Motion: For subtle animations that improve user experience without being distracting.
- Tocbot: For automatically generating and updating table of contents from markdown headings.
Syntax Highlighting & Code Display
- Rehype Pretty Code: For enhanced code syntax highlighting with line numbers, line highlighting, and proper theming.
- Rehype GitHub Slug: For automatically generating anchor links for headings.
Development Process
Planning & Design
- Started with defining content architecture for both projects and blog posts
- Designed schema for content types including posts, projects, and series
- Created wireframes for key pages before implementation
- Established a color scheme and typography system
Implementation Approach
I followed a component-driven development approach:
- Built core layout components (Header, Footer, Navigation)
- Implemented content schema and processing pipeline
- Developed individual page templates for different content types
- Added interactive elements and optimizations
- Implemented responsive design across all breakpoints
Key Challenges & Solutions
Challenge 1: Blog Series Implementation
Problem: Needed to create a system for blog post series that shows relationships between posts, maintains correct ordering, and provides navigation.
Solution: Implemented a nested Series type and navigation utilities:
// Series definition
export const Series = defineNestedType(() => ({
name: "Series",
fields: {
title: {
type: "string",
required: true,
},
order: {
type: "number",
required: true,
},
},
}));
// Series navigation helper
export function getSeriesNavigation(currentPost: Post): {
seriesTitle: string;
currentIndex: number;
totalPosts: number;
prevPost: Post | null;
nextPost: Post | null;
allPosts: Post[];
} | null {
if (!currentPost.series) return null;
const seriesTitle = currentPost.series.title;
const allPosts = getAllSeriesPostsWithDrafts(seriesTitle);
const currentIndex = allPosts.findIndex(
(post) => post.slug === currentPost.slug
);
// Navigation logic...
return {
seriesTitle,
currentIndex,
totalPosts: allPosts.length,
prevPost,
nextPost,
allPosts,
};
}
Challenge 2: Image Optimization with MDX Content
Problem: While Next.js provides excellent image optimization through its Image component, it has significant limitations when working with MDX content:
- Next Image component requires explicit width, height, and src props that are unknown for markdown images
- Images referenced in MDX are written as

and are relative to the content file, not the public directory - Next.js doesn't natively optimize images in MDX content during the build process
- Standard markdown transformers don't extract image dimensions needed by Next Image
Solution Part 1: Created a custom image processor for featured images in frontmatter:
export const createImageProcessor =
(contentType: string) => async (doc: any) => {
try {
// Get image from relative path in content directory
const contentRootDir = path.join(process.cwd(), "src/content");
const relativeDir = doc._raw.sourceFileDir;
const featuredImageRootPath = path.join(contentRootDir, relativeDir);
const imagePath = path.join(featuredImageRootPath, doc.featuredImageUrl);
// Process with Sharp to get metadata and create blur placeholder
const image = sharp(imagePath);
const metadata = await image.metadata();
const width = metadata.width ?? 0;
const height = metadata.height ?? 0;
// Generate blur data URL for placeholder
const blurdataurl = await image
.resize(8, Math.round(8 / imgAspectRatio))
.png({ quality: 75 })
.toBuffer()
.then((buffer) => `data:image/png;base64,${buffer.toString("base64")}`);
// Use content hash to prevent duplicates and enable cache invalidation
const fileHash = await generateFileHash(imagePath);
// Copy to public directory with hash in filename for proper caching
const destDir = path.join(publicDir, relativeDir, fileNameWithHash);
const src = path.join(`/assets/${relativeDir}`, fileNameWithHash);
// Now images are accessible to Next.js Image component with all required props
return {
src,
width,
height,
blurdataurl,
};
} catch (error) {
// Error handling...
}
};
Solution Part 2: Created a custom Rehype plugin (static-media.ts) to transform inline markdown images:
This plugin processes any <img>
tags in the MDX content during build time. It:
- Locates the original image relative to the MDX file
- Processes it with Sharp to extract dimensions
- Generates a blur placeholder image
- Creates a content hash for cache invalidation
- Copies the image to the public directory
- Transforms the image tag to include all props needed by Next.js Image
The solution effectively bridges the gap between Markdown's simple image syntax and Next.js Image component's requirements.
Code Architecture Decisions
Content-Driven Architecture
I designed the architecture around content types with clear separation between:
- Content Storage: Markdown files organized by content type in src/content/
- Content Processing: ContentLayer configuration and utility functions
- Component Rendering: React components for different content views
- Page Templates: Next.js page components that integrate content and UI
Component Organization
Components are organized by functionality:
- Shared Components: Reusable UI elements (buttons, tags, sections)
- Feature-Specific Components: Components for blog, projects, hero section
- Layout Components: Header, footer, and page layout components
Data Flow & Content Service
I created a content-service.ts
module that centralizes all content fetching logic, providing a clean API for page components. This approach:
- Encapsulates ContentLayer-specific implementation details
- Provides a unified interface for both blog and project content
- Handles environment-specific logic (showing drafts in development only)
- Manages series relationships and navigation
Lazy Loading
Lazy-loaded images with blur placeholders
Key Features
The portfolio incorporates a robust MDX rendering system featuring:
- Syntax highlighting for code blocks with support for multiple languages
- Line highlighting for emphasizing specific code sections
- Automatic code block copy functionality
- Custom component integration in markdown content
A complete series management system that includes:
- Automated series navigation with previous/next post links
- Series progress tracking with visual indicators
- Series table of contents with expandable post list
- Support for planned posts in series (visible but not accessible)
Detailed project documentation pages featuring:
- Technology tag system with icon representation
- Project categorization and filtering
- Detailed markdown documentation with table of contents
- GitHub integration for source code access
A fully responsive design with:
- Mobile-first approach with adaptive layouts
- Custom navigation for mobile devices
- Optimized typography and spacing across device sizes
- Performance optimizations for mobile networks
Solved the challenging problem of integrating Next.js Image optimization with MDX content through:
- Custom image processing for both featured images and inline MDX images
- Automatic generation of width, height and blur placeholders for all images
- Content-hash based file naming for proper cache invalidation
- Rehype plugin (static-media.ts) that transforms inline markdown images during the build process
Performance & Outcomes
Performance Metrics
-
Lighthouse Scores:
- Performance: 95+
- Accessibility: 98+
- Best Practices: 95+
- SEO: 100
-
Core Web Vitals:
- Largest Contentful Paint (LCP): < 1.5s
- First Input Delay (FID): < 100ms
- Cumulative Layout Shift (CLS): < 0.1
SEO Optimization
- Semantic HTML structure
- Proper heading hierarchy
- Meta descriptions and OpenGraph tags
- Structured data for blog posts
- Sitemap generation
- robots.txt
Content Management Efficiency
- Simplified content publishing workflow
- Type-safe content with schema validation
- Automated image processing
- Draft/published status system
Future Enhancements
- Search Functionality: Adding full-text search across projects and blog posts
- Accessibility: Work more on accessibility
- Dark/Light Theme Toggle: Supporting user preference for color scheme
- Newsletter Integration: Adding email subscription for blog updates
- Analytics Integration: Adding privacy-focused analytics to track content performance