← All posts

Blog · December 20, 2025 · 5 min read
Building My Portfolio with Next.js 16, Tailwind v4 & Framer Motion
A behind-the-scenes look at the design decisions, performance optimisations, and animation techniques used to build this very site.
#Next.js#React#Tailwind CSS#Web Dev
Why Next.js 16?
Next.js 16 ships with React 19 and the App Router as the default. The file-system routing, built-in metadata API, and server components make it ideal for a content-heavy portfolio.
Tailwind v4, A Different Mental Model
Tailwind v4 drops the tailwind.config.js file. Configuration moves into globals.css using @theme:
@import "tailwindcss";
@theme inline {
--color-accent-indigo: #6366f1;
--color-accent-cyan: #06b6d4;
--font-display: "Space Grotesk", sans-serif;
}
These become CSS variables and Tailwind utilities automatically. So bg-accent-indigo just works.
Framer Motion Patterns
Reveal on Scroll
"use client";
import { motion, useInView } from "framer-motion";
import { useRef } from "react";
export function Reveal({ children }: { children: React.ReactNode }) {
const ref = useRef(null);
const inView = useInView(ref, { once: true, margin: "-80px" });
return (
<motion.div
ref={ref}
initial={{ opacity: 0, y: 24 }}
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease: [0.4, 0, 0.2, 1] }}
>
{children}
</motion.div>
);
}
Staggered Children
const container = {
hidden: {},
show: {
transition: { staggerChildren: 0.08 }
}
};
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0, transition: { duration: 0.5 } }
};
<motion.ul variants={container} initial="hidden" animate="show">
{items.map(i => (
<motion.li key={i} variants={item}>{i}</motion.li>
))}
</motion.ul>
Performance Notes
- All heavy components use
"use client"only where needed, server components handle static markup. - Images served from
/public, no external image domains needed. - Font loading via
next/font/googlewithdisplay: swapto avoid layout shift.
What I'd Do Differently
- MDX for blog posts, richer than plain markdown, composable React components inline.
- View Transitions API, browser-native page transitions instead of Framer Motion for route changes.
- Incremental Static Regeneration, for blog posts that update infrequently.
The full source is on GitHub.

Sohaib Shamsi
I build the systems money moves through.