Building My Portfolio Website: Modern Next.js with Advanced MDX Blog System

Today

Portfolio Website

Table of Contents

  1. Introduction
  2. Goals
  3. Architecture Overview
  4. Design System & Components
  5. MDX Blog Pipeline
  6. Animations & UX
  7. SEO & Metadata
  8. Performance
  9. Deployment
  10. Lessons Learned
  11. Future Enhancements
  12. Conclusion

Introduction

This site is a compact, fast portfolio built on the Next.js App Router. It combines a clean visual language with subtle motion, a dock-style navbar, and an MDX-powered blog that supports Git-backed posts, code highlighting, and raw HTML where needed.

Goals

Architecture Overview

// Root layout sets up SEO, theme, and global shell (excerpt)
export const metadata = { /* pulled from DATA in resume */ };
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className="min-h-screen bg-background font-sans antialiased max-w-2xl mx-auto py-12 sm:py-24 px-6">
        <ThemeProvider attribute="class" defaultTheme="light">
          <TooltipProvider delayDuration={0}>{children}<Navbar /></TooltipProvider>
        </ThemeProvider>
        <Analytics />
      </body>
    </html>
  );
}

Design System & Components

The UI leans on small, composable components with a custom dock-style navbar.

// Dock navbar (excerpt)
<Dock className="mx-auto flex items-center px-1 bg-background">
  {DATA.navbar.map((item) => (
    <DockIcon key={item.href}>
      <Tooltip>
        <TooltipTrigger asChild>
          <Link href={item.href} className={buttonVariants({ variant: "ghost", size: "icon" }) + " size-12"}>
            <item.icon className="size-4" />
          </Link>
        </TooltipTrigger>
        <TooltipContent><p>{item.label}</p></TooltipContent>
      </Tooltip>
    </DockIcon>
  ))}
  {/* social icons + theme toggle */}
  <DockIcon><ModeToggle /></DockIcon>
}</Dock>

Project tiles support both images and autoplaying videos, with tags and external links.

// ProjectCard media handling (excerpt)
{video && (
  <video src={video} autoPlay loop muted playsInline className="h-40 w-full object-cover" />
)}
{image && (
  <Image src={image} alt={title} width={500} height={300} className="h-40 w-full object-cover" />
)}

MDX Blog Pipeline

Posts live under content/*.mdx with frontmatter metadata. The pipeline converts MD/MDX to HTML using unified with remark/rehype plugins, including GitHub-flavored markdown, raw HTML, slugged headings, and Shiki-powered code highlighting via rehype-pretty-code.

// content loader and pipeline (excerpt)
export async function markdownToHTML(markdown: string) {
  const p = await unified()
    .use(remarkParse)
    .use(remarkGfm)
    .use(remarkRehype, { allowDangerousHtml: true })
    .use(rehypeRaw)
    .use(rehypeSlug)
    .use(rehypePrettyCode, {
      theme: { light: "min-light", dark: "min-dark" },
      keepBackground: false,
    })
    .use(rehypeStringify)
    .process(markdown);
  return p.toString();
}

Each blog post page generates per-post metadata and JSON-LD for SEO.

// Per-post metadata (excerpt)
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
  const post = await getPost(params.slug);
  const ogImage = post.metadata.image ? `${DATA.url}${post.metadata.image}` : `${DATA.url}/og?title=${post.metadata.title}`;
  return {
    title: post.metadata.title,
    description: post.metadata.summary,
    openGraph: { type: "article", title: post.metadata.title, description: post.metadata.summary, images: [ogImage] },
    twitter: { card: "summary_large_image", title: post.metadata.title, description: post.metadata.summary, images: [ogImage] },
  };
}

Animations & UX

Subtle entrance animations improve perceived performance and polish. BlurFade reveals blocks on scroll; BlurFadeText animates headings or sentences, optionally by character.

// BlurFade (excerpt)
const defaultVariants = { hidden: { y: 6, opacity: 0, filter: "blur(6px)" }, visible: { y: -6, opacity: 1, filter: "blur(0px)" } };
<motion.div initial="hidden" animate="visible" variants={defaultVariants} transition={{ delay: 0.04 + delay, duration: 0.4, ease: "easeOut" }}>
  {children}
</motion.div>

SEO & Metadata

Global metadata comes from a single source of truth in DATA and is applied site‑wide in layout.tsx, including OpenGraph and Twitter cards. Blog posts add structured data via JSON‑LD.

Performance

Lessons Learned

Future Enhancements

Conclusion

This portfolio focuses on clarity, speed, and maintainability. With a tight Next.js setup, a pleasant motion system, and an MDX pipeline that’s easy to extend, it’s a solid foundation for showcasing work and writing long-form content.