How I Built My Portfolio with Next.js and Vercel
A deep dive into building a modern, performant, and SEO-optimized developer portfolio using Next.js 13, TypeScript, Tailwind CSS, and Vercel. Learn the architecture decisions, performance optimizations, and best practices I used.
Introduction
As a Full-Stack Developer, having a professional portfolio is essential for showcasing your skills to recruiters and potential clients. After evaluating various frameworks and approaches, I chose to build my portfolio with Next.js 13, TypeScript, and Tailwind CSS, deployed on Vercel.
In this article, I'll walk you through the key decisions, architecture, and optimizations that make this portfolio stand out. Whether you're a developer looking to build your own portfolio or simply curious about modern web development practices, this guide will provide valuable insights.
Why Next.js?
Next.js has become my go-to framework for React applications, and here's why it's perfect for a portfolio:
Server-Side Rendering & Static Generation
Next.js offers multiple rendering strategies out of the box:
// Static Site Generation (SSG) - Perfect for portfolios
export async function generateStaticParams() {
const posts = getAllPostSlugs();
return posts.map((slug) => ({ slug }));
}
// This generates pages at build time
export default function BlogPost({ params }: { params: { slug: string } }) {
const post = getPostBySlug(params.slug);
return <Article post={post} />;
}
For a portfolio, Static Site Generation (SSG) is ideal because:
- Content doesn't change frequently
- Pages load instantly (pre-rendered HTML)
- Excellent SEO (search engines can crawl content easily)
- Zero server costs (purely static files)
App Router Architecture
Next.js 13 introduced the App Router, which I leveraged for better organization:
app/
├── layout.tsx # Root layout with providers
├── page.tsx # Home page
├── blog/
│ ├── page.tsx # Blog listing
│ └── [slug]/
│ └── page.tsx # Individual blog posts
└── globals.css
This file-based routing makes the codebase intuitive and maintainable.
TypeScript for Type Safety
TypeScript isn't just about catching errors—it's about documenting your code and enabling better tooling:
// Type definitions for blog posts
export type BlogPostFrontmatter = {
title: string;
description: string;
author: string;
date: string;
tags: string[];
coverImage: string;
published: boolean;
seoTitle?: string;
seoDescription?: string;
};
export type BlogPost = {
slug: string;
frontmatter: BlogPostFrontmatter;
content: string;
readingTime: {
text: string;
minutes: number;
words: number;
};
};
With these types, my IDE provides autocomplete, catches typos, and makes refactoring safe.
Styling with Tailwind CSS
I chose Tailwind CSS for its utility-first approach and incredible developer experience:
// Clean, readable component with Tailwind
function ProjectCard({ project }: { project: Project }) {
return (
<div className="group relative overflow-hidden rounded-xl border border-border bg-card transition-all hover:shadow-lg">
<div className="aspect-video relative">
<Image
src={project.image}
alt={project.title}
fill
className="object-cover transition-transform group-hover:scale-105"
/>
</div>
<div className="p-6">
<h3 className="text-xl font-bold text-foreground mb-2">
{project.title}
</h3>
<p className="text-muted-foreground line-clamp-2">
{project.description}
</p>
</div>
</div>
);
}
CSS Variables for Theming
I use CSS variables for a robust dark/light mode system:
:root {
--background: 0 0% 98%;
--foreground: 215 25% 15%;
--primary: 0 84% 60%;
--muted: 215 20% 95%;
}
.dark {
--background: 224 27% 6%;
--foreground: 210 40% 95%;
--primary: 0 90% 65%;
--muted: 224 27% 12%;
}
This approach allows seamless theme switching without complex logic.
Performance Optimizations
Performance is crucial for user experience and SEO. Here's what I implemented:
Image Optimization
Next.js Image component handles optimization automatically:
import Image from 'next/image';
<Image
src={post.coverImage}
alt={post.title}
fill
priority // Load above-the-fold images immediately
sizes="(max-width: 768px) 100vw, 60vw"
className="object-cover"
/>
Code Splitting
Next.js automatically splits code by route, but I also use dynamic imports for heavy components:
import dynamic from 'next/dynamic';
// Only load when needed
const ProjectsSection = dynamic(
() => import('@/components/sections/projects'),
{ loading: () => <ProjectsSkeleton /> }
);
Font Optimization
Using next/font eliminates layout shift and optimizes loading:
import { Syne } from 'next/font/google';
const syne = Syne({
subsets: ['latin'],
weight: ['400', '500', '600', '700', '800'],
display: 'swap',
});
SEO Implementation
For a portfolio, SEO is critical. Here's my comprehensive approach:
Dynamic Metadata
export async function generateMetadata({ params }): Promise<Metadata> {
const post = getPostBySlug(params.slug);
return {
title: `${post.frontmatter.title} | Mouhssine Lakhili`,
description: post.frontmatter.description,
openGraph: {
title: post.frontmatter.title,
description: post.frontmatter.description,
type: 'article',
publishedTime: post.frontmatter.date,
authors: [post.frontmatter.author],
},
twitter: {
card: 'summary_large_image',
title: post.frontmatter.title,
description: post.frontmatter.description,
},
};
}
Structured Data (JSON-LD)
I add schema markup for rich search results:
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.frontmatter.title,
description: post.frontmatter.description,
author: {
'@type': 'Person',
name: 'Mouhssine Lakhili',
},
datePublished: post.frontmatter.date,
image: post.frontmatter.coverImage,
};
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
Automated Sitemap Generation
A pre-build script generates the sitemap:
// scripts/generate-sitemap.js
function generateSitemap() {
const blogPosts = getBlogPosts();
const urls = [
{ loc: SITE_URL, priority: '1.0' },
{ loc: `${SITE_URL}/blog`, priority: '0.8' },
...blogPosts.map(post => ({
loc: `${SITE_URL}/blog/${post.slug}`,
priority: '0.7',
lastmod: post.date,
})),
];
// Generate XML...
}
Internationalization (i18n)
My portfolio supports English, French, and Spanish:
const translations = {
en: {
'nav.home': 'Home',
'nav.about': 'About',
'nav.projects': 'Projects',
// ...
},
fr: {
'nav.home': 'Accueil',
'nav.about': 'À propos',
'nav.projects': 'Projets',
// ...
},
es: {
'nav.home': 'Inicio',
'nav.about': 'Acerca de',
'nav.projects': 'Proyectos',
// ...
},
};
Using React Context, language switching is smooth and persists across sessions.
Deployment on Vercel
Vercel provides the best experience for Next.js deployments:
- Zero Configuration: Just connect your GitHub repo
- Preview Deployments: Every PR gets its own URL
- Edge Network: Content served from 100+ global locations
- Analytics: Built-in performance monitoring
# That's literally all you need
vercel deploy
Key Takeaways
Building this portfolio taught me several important lessons:
- Start with performance in mind: It's easier to maintain fast than to make slow fast
- Type everything: TypeScript catches bugs before users do
- SEO from day one: Structure your content for search engines early
- Accessibility matters: Use semantic HTML and ARIA labels
- Keep it simple: Over-engineering is the enemy of shipping
What's Next?
I'm continuously improving this portfolio. Future enhancements include:
- RSS feed for the blog
- View count tracking
- Newsletter integration
- More interactive components
Conclusion
Building a portfolio is more than just showcasing projects—it's an opportunity to demonstrate your skills in action. Every line of code, every design decision, and every optimization tells potential employers and clients about your capabilities.
If you're considering building your own portfolio, I encourage you to start with Next.js. The developer experience is excellent, the performance is outstanding, and the ecosystem is thriving.
Feel free to explore my GitHub, my profile page, my hiring page, or my freelance page if you have questions!
Related articles
Explore more development guides:
- AI-Powered Developer Workflows — Use AI tools to accelerate your portfolio development.
- How Developers Will Work in 2030 — Future skills every developer needs.
- The Tech Survival Map 2026 — Understand the tech landscape shaping modern development.
Want to discuss this article or have questions about building your own portfolio? Connect with me on LinkedIn or Twitter.
Build with AI and ship with confidence
Need a developer who can turn ideas into production work?
I help teams ship React, Next.js, Node.js, AI, and automation work with clear scope, practical guardrails, and fast execution.
Related articles
Next.js Developer Portfolio SEO Checklist: How to Get Interviews in 2026
A practical SEO checklist for a Next.js developer portfolio: branding, conversion pages, schema, images, internal links, and the signals that help recruiters or clients discover you.
How AI Agents Actually Work: Architecture, Memory, Tools, and the Agent Loop
A technical walkthrough of AI agent architecture: the agent loop, tool use, memory (RAG/vector DBs), evaluation, and common production failure modes.
Why AI Agents Fail (And How to Fix Them)
A practical guide to AI agent failures in production and how to fix them with better prompts, memory design, tool gating, evaluation, UX, and security.
