Skip to main content
Blog

Building My Personal Website

10 mins read

Welcome to my first blog post! Starting something new always feels exciting and a bit uncertain. Today, I want to share how I built this website — the technologies I chose, the problems I solved, and the lessons I learned along the way.

Why I Built This Site

Every developer needs a place to share their work, thoughts, and experiments. I wanted a website that was:

  • Fast — Lightning-quick load times with static generation
  • SEO-friendly — Easy for search engines to discover and rank
  • Maintainable — Clean code with type safety and modern tools
  • Accessible — Usable by everyone, regardless of ability

Let me walk you through how I achieved these goals.

The Foundation: Core Technologies

Next.js 16 — The Framework

I chose Next.js 16 as my foundation. I've used Next.js since version 13, and it continues to impress with its developer experience and performance features.

What makes Next.js great for this project:

  • Server Components — Components render on the server by default, sending less JavaScript to browsers
  • Static Generation — Pages are pre-built as HTML files for instant loading
  • Image Optimization — Automatic image resizing, format conversion, and lazy loading
  • File-based Routing — The folder structure becomes your site's URL structure

Resources: Next.js DocumentationApp Router Guide

TypeScript — Type Safety

TypeScript catches bugs before they reach production. Every function, component, and configuration is fully typed, making refactoring safe and development faster with autocomplete support.

Resources: TypeScript Documentation

Styling: Building a Consistent Design System

Tailwind CSS v4 — Utility-First Styling

I'm using Tailwind CSS v4, which brings exciting new features like improved CSS variable support.

My approach to styling:

Instead of using colors directly in components, I created reusable utility classes in my CSS file. This keeps the code clean and makes design changes easier.

Theme System — Light and Dark Modes

The site supports both light and dark modes with automatic detection of your system preference. There's a theme toggle in the bottom right corner to switch manually.

How it works:

  1. CSS variables store all colors with different values for each theme
  2. A small JavaScript snippet runs before React loads to prevent flash of the wrong theme
  3. The theme toggle updates a data-theme attribute on the <html> element
  4. CSS automatically applies the correct colors based on this attribute

Resources: Tailwind CSS v4OKLCH Colors

Content Management: Writing with MDX

Why MDX?

MDX combines the simplicity of Markdown with the power of React components. This means I can write blog posts in Markdown and embed interactive React components directly in the content.

Example:

##My Blog Post

This is regular Markdown text.

<Button>Click me!</Button>

This button is a real React component!

Custom Component Configuration

I've configured MDX to use custom components throughout the site:

import { MDXComponents } from "mdx/types";
import { Button } from "@/components/button";
import NextLink from "next/link";

export function useMDXComponents(components: MDXComponents): MDXComponents {
    return {
        ...components,
        Button,
        Link: NextLink, // Use Next.js Link for better navigation
        h1: (props) => <h1 {...props} />,
        // ... other elements
    };
}

This ensures all content follows the same design system and accessibility patterns.

Resources: MDX DocumentationNext.js MDX Guide

Content Collections — Type-Safe Content

While Next.js supports MDX natively, I needed more robust content management. Enter Content Collections.

Every blog post's metadata (title, summary, tags, etc.) is validated against a schema. If something is missing or wrong, the build fails with a clear error message.

Resources: Content CollectionsZod Validation

Static Site Generation

The entire website is pre-built as static HTML files. This means:

  • Instant Loading — No server processing time
  • Better SEO — Search engines can crawl HTML directly
  • Lower Costs — No server to run
  • High Reliability — Static files served from a CDN can handle traffic spikes

Blog posts are generated at build time from MDX files, with Content Collections ensuring type safety throughout the process.

SEO: Being Discoverable

Building a website is one thing. Making sure people can find it is another. Here's my SEO strategy:

Metadata API

Next.js provides a powerful Metadata API for managing meta tags. Every page has:

  • Unique titles and descriptions optimized for search engines
  • Open Graph tags for rich previews on social media
  • Twitter Card metadata for enhanced sharing on Twitter/X
  • Canonical URLs to prevent duplicate content issues
export function getBlogPostMetadata(blog: BlogPost, slug: string): Metadata {
    return {
        title: blog.title,
        description: blog.summary,
        openGraph: {
            title: blog.title,
            description: blog.summary,
            url: `/blogs/${slug}`,
            images: [`/blogs/${slug}/opengraph-image`],
        },
        alternates: {
            canonical: `/blogs/${slug}`,
        },
    };
}

Dynamic Open Graph Images

Each page has a unique social media preview image, generated automatically using next/og.

How it works:

Next.js detects opengraph-image.tsx files in your routes and generates images at build time using Satori — a library that converts JSX to images.

For static pages:

export default async function Image() {
    return new ImageResponse(
        (
            <div
                style={
                    {
                        /* design */
                    }
                }
            >
                Blog Posts | Mudasir Pandith
            </div>
        ),
        { width: 1200, height: 630 }
    );
}

For blog posts (dynamic):

export default async function Image({ params }) {
    const { slug } = await params;
    const blog = allBlogs.find((b) => getSlug(b.title) === slug);

    return new ImageResponse(
        (
            <div
                style={
                    {
                        /* design */
                    }
                }
            >
                <h1>{blog.title}</h1>
                <p>{blog.summary}</p>
            </div>
        ),
        { width: 1200, height: 630 }
    );
}

Benefits:

  • Unique preview image for every blog post
  • Better click-through rates on social media
  • Professional appearance when sharing links
  • Consistent branding across all shared content

Resources: Next.js OG Images

JSON-LD Structured Data

JSON-LD helps search engines understand your content better, potentially leading to rich results in search listings.

What I've implemented:

Person Schema

Describes me as the author:

{
    "@type": "Person",
    "name": "Mudasir Pandith",
    "jobTitle": "Software Developer",
    "url": "https://mudasirpandith.com",
    "sameAs": ["https://github.com/mudasirpandith", "https://linkedin.com/in/mudasirpandith"]
}

WebSite Schema

Describes the website itself:

{
    "@type": "WebSite",
    "name": "Mudasir Pandith",
    "url": "https://mudasirpandith.com",
    "author": { "@type": "Person", "name": "Mudasir Pandith" }
}

BlogPosting Schema

For each blog post:

{
    "@type": "BlogPosting",
    "headline": "Building My Personal Website",
    "description": "A technical deep dive...",
    "datePublished": "2024-01-15",
    "author": { "@type": "Person", "name": "Mudasir Pandith" },
    "keywords": ["nextjs", "web-development"]
}

Implementation:

export function JsonLd({ data }) {
    return <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} />;
}

Benefits:

  • Rich snippets in search results
  • Better understanding by search engines
  • Improved voice search compatibility
  • Potential Knowledge Graph inclusion

Validation Tools:

Resources: JSON-LD SpecificationSchema.org Guide

Robots.txt & Sitemap

Robots.txt tells search engines what they can crawl:

export default function robots() {
    return {
        rules: {
            userAgent: "*",
            allow: "/",
        },
        sitemap: "https://mudasirpandith.com/sitemap.xml",
    };
}

Sitemap.xml lists all pages with metadata:

export default function sitemap() {
    return [
        {
            url: "https://mudasirpandith.com",
            lastModified: new Date(),
            changeFrequency: "weekly",
            priority: 1,
        },
        {
            url: "https://mudasirpandith.com/blogs",
            lastModified: new Date(),
            changeFrequency: "weekly",
            priority: 0.8,
        },
    ];
}

Resources: Google Sitemap Guidelines

Hosting: Vercel

I'm hosting on Vercel, which is built by the creators of Next.js. It provides:

  • Zero Configuration — Detects Next.js automatically and configures everything
  • Global CDN — Fast loading worldwide with edge caching
  • Automatic Deployments — Every push to GitHub triggers a new deployment
  • Preview URLs — Every pull request gets its own preview link
  • Performance Analytics — Built-in monitoring of Core Web Vitals

While I'm exploring Cloudflare Pages for future projects, Vercel remains an excellent choice for Next.js applications.

Resources: Vercel Documentation

Wrapping Up

Building this website taught me a lot about modern web development. The combination of Next.js, React, TypeScript, and thoughtful tooling creates a solid foundation for a fast, maintainable, and accessible website.

I'm excited to share more technical insights, lessons learned, and development workflows in future posts. If you have questions about any of these topics, feel free to reach out!