# Building This Site: A Modern Web Development Journey Building a personal website is one of the best ways to learn modern web development. In this post, I'll walk you through exactly how I built this site, explaining each technology choice and the reasoning behind it. ## The Foundation: Why These Technologies? ### Next.js 14 with App Router I chose **Next.js** because it's the most popular React framework, offering: - **Server-Side Rendering (SSR)** and **Static Site Generation (SSG)** out of the box
- **App Router** - the new file-based routing system that's more intuitive
- **Built-in optimization** for images, fonts, and JavaScript bundles
- **Great developer experience** with hot reloading and TypeScript support The **App Router** (vs the older Pages Router) uses a `app/` directory where:
- `layout.tsx` defines shared UI elements across pages
- `page.tsx` files automatically become routes
- Folders create nested routes (like `app/writing/page.tsx` → `/writing`) ### TypeScript for Type Safety **TypeScript** adds static type checking to JavaScript, which means: ```typescript
interface PostFrontmatter { title: string summary: string date: string tags: string[] published: boolean
}
``` This prevents bugs by catching errors at compile time rather than runtime. For example, if I try to access `post.titel` (misspelled), TypeScript will immediately flag this error. ### Tailwind CSS for Styling **Tailwind CSS** is a utility-first CSS framework that lets you style directly in your HTML: ```tsx
<div className="max-w-3xl mx-auto px-4 py-8"> <h1 className="text-3xl font-semibold text-neutral-900 dark:text-neutral-100"> My Title </h1>
</div>
``` Benefits:
- **No CSS files to manage** - styles are co-located with components
- **Consistent design system** - predefined spacing, colors, and typography
- **Dark mode support** built-in with `dark:` prefix
- **Responsive design** with `sm:`, `md:`, `lg:` prefixes ## Project Structure and Architecture Here's how I organized the codebase: ```
├── app/ # Next.js App Router pages
│ ├── layout.tsx # Root layout with header/footer
│ ├── page.tsx # Homepage
│ └── writing/ # Blog section
│ ├── page.tsx # Blog index
│ └── [slug]/ # Dynamic blog post pages
├── components/ # Reusable UI components
├── content/ # Blog posts and data
│ └── writing/ # MDX blog posts
├── lib/ # Utility functions
│ ├── content.ts # Content loading logic
│ └── mdx.tsx # MDX processing
└── public/ # Static assets
``` This separation of concerns makes the code:
- **Easier to maintain** - each file has a single responsibility
- **Reusable** - components can be used across multiple pages
- **Scalable** - easy to add new sections or features ## The Content Management System ### Why MDX Over a Traditional CMS? I chose **MDX** (Markdown + JSX) for blog posts because: 1. **Git-based workflow** - content lives in the repository
2. **No database required** - perfect for static sites
3. **Rich content** - can embed React components in markdown
4. **Developer-friendly** - write in your favorite editor ### Content Loading with Gray Matter Instead of using Contentlayer (which has Next.js 14 compatibility issues), I built a custom content system: ```typescript
export function getAllPosts(): Post[] { const writingDir = path.join(contentDirectory, 'writing') const filenames = fs.readdirSync(writingDir) const posts = filenames .filter((name) => name.endsWith('.mdx')) .map((name) => { const slug = name.replace(/\.mdx$/, '') const fullPath = path.join(writingDir, name) const fileContents = fs.readFileSync(fullPath, 'utf8') const { data, content } = matter(fileContents) return { slug, frontmatter: data as PostFrontmatter, content, readingTime: readingTime(content).text, } }) .filter((post) => post.frontmatter.published) .sort((a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime()) return posts
}
``` This function:
1. **Reads all MDX files** from the content directory
2. **Parses frontmatter** (title, date, tags) using gray-matter
3. **Calculates reading time** using the reading-time library
4. **Filters published posts** and sorts by date
5. **Returns typed data** that's safe to use throughout the app ### MDX Processing Pipeline For rich content rendering, I set up an MDX processing pipeline: ```typescript
export const mdxOptions = { mdxOptions: { remarkPlugins: [remarkGfm], // GitHub Flavored Markdown rehypePlugins: [rehypeSlug], // Auto-generate heading IDs },
}
``` - **remarkGfm** adds support for tables, strikethrough, task lists
- **rehypeSlug** automatically generates IDs for headings (for table of contents)
- **next-mdx-remote** renders the processed MDX on the client ## Component Architecture ### Layout System The root layout (`app/layout.tsx`) wraps all pages: ```tsx
export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en" suppressHydrationWarning> <body className={inter.className}> <ThemeProvider attribute="class" defaultTheme="system" enableSystem> <div className="min-h-screen flex flex-col"> <Header /> <main className="flex-1 container max-w-3xl mx-auto px-4 py-8"> {children} </main> <Footer /> </div> </ThemeProvider> </body> </html> )
}
``` Key concepts:
- **Flexbox layout** ensures footer stays at bottom
- **Container with max-width** creates readable line lengths
- **ThemeProvider** enables dark mode functionality
- **suppressHydrationWarning** prevents theme-related hydration mismatches ### Dark Mode Implementation Using `next-themes` for dark mode: ```tsx
'use client'
import { useTheme } from 'next-themes' export function ThemeToggle() { const { setTheme, theme } = useTheme() return ( <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')} className="p-2 rounded-md hover:bg-neutral-100 dark:hover:bg-neutral-800" > {/* Theme icons */} </button> )
}
``` Benefits of this approach:
- **System preference detection** - respects user's OS theme
- **Persistent choice** - remembers user preference in localStorage
- **No flash of unstyled content** - prevents theme flickering on page load ## Dynamic Routing and Static Generation ### Blog Post Pages The `[slug]` directory creates dynamic routes: ```tsx
// app/writing/[slug]/page.tsx
export async function generateStaticParams() { const posts = getAllPosts() return posts.map((post) => ({ slug: post.slug, }))
} export default function PostPage({ params }: { params: { slug: string } }) { const post = getPostBySlug(params.slug) if (!post || !post.frontmatter.published) { notFound() } return ( <article className="prose prose-neutral dark:prose-invert max-w-none"> <MDXContent content={post.content} /> </article> )
}
``` **generateStaticParams** tells Next.js which pages to pre-build at build time. This means:
- **Faster page loads** - HTML is pre-generated
- **Better SEO** - search engines can crawl static HTML
- **Reduced server load** - no server rendering needed ## Production Optimization and Deployment ### Next.js Configuration for Performance ```javascript
// next.config.mjs
const nextConfig = { output: 'export', // Generate static files trailingSlash: true, // Consistent URLs images: { unoptimized: true }, // For static hosting compiler: { removeConsole: process.env.NODE_ENV === 'production', // Remove console.logs }, swcMinify: true, // Fast JavaScript minification
}
``` Each setting has a purpose:
- **output: 'export'** generates a static site that can be hosted anywhere
- **trailingSlash: true** ensures consistent URLs for better SEO
- **removeConsole** keeps production builds clean
- **swcMinify** uses Rust-based SWC for faster builds ### Cloudflare Pages Deployment I chose **Cloudflare Pages** for hosting because: 1. **Global CDN** - fast loading worldwide
2. **Automatic HTTPS** - secure by default
3. **Branch previews** - test changes before going live
4. **Edge computing** - run code closer to users
5. **Free tier** - generous limits for personal sites The deployment process:
1. **Git integration** - automatically deploys on push to main branch
2. **Build optimization** - Cloudflare minifies and compresses assets
3. **Cache headers** - static assets cached for optimal performance ### Performance Optimizations The final site achieves excellent performance through: - **Static generation** - all pages pre-built as HTML
- **Tree shaking** - unused code automatically removed
- **Code splitting** - JavaScript loaded on-demand
- **Optimal caching** - static assets cached for 1 year
- **Gzip compression** - reduced file sizes ## SEO and Accessibility ### Metadata Generation Each page generates appropriate metadata: ```tsx
export async function generateMetadata({ params }: PostPageProps) { const post = getPostBySlug(params.slug) return { title: post.frontmatter.title, description: post.frontmatter.summary, }
}
``` This creates proper `<title>` and `<meta>` tags for each blog post, improving search engine visibility. ### Semantic HTML and Accessibility The site uses semantic HTML elements:
- `<article>` for blog posts
- `<nav>` for navigation
- `<main>` for primary content
- Proper heading hierarchy (`h1`, `h2`, `h3`) This helps screen readers and search engines understand the content structure. ## Development Workflow ### Tools for Code Quality - **ESLint** - catches common JavaScript errors
- **Prettier** - automatically formats code consistently
- **TypeScript** - prevents type-related bugs
- **GitHub Actions** - automatically tests and deploys ### Bundle Analysis The `@next/bundle-analyzer` helps optimize performance: ```bash
npm run build:analyze
``` This shows which parts of your JavaScript bundle are largest, helping identify optimization opportunities. ## What's Next? This foundation supports future enhancements: 1. **Search functionality** - add blog post search
2. **Analytics** - integrate privacy-friendly analytics
3. **Newsletter** - add email subscription
4. **Comments** - integrate with GitHub Discussions
5. **More content types** - projects portfolio, case studies ## Key Takeaways Building this site taught me several important web development principles: 1. **Start simple** - get a working version first, then optimize
2. **Choose technologies wisely** - each tool should solve a specific problem
3. **Plan for scale** - organize code to handle future features
4. **Performance matters** - optimize for both developers and users
5. **Documentation is crucial** - document decisions for future reference The combination of Next.js, TypeScript, Tailwind CSS, and Cloudflare Pages creates a modern, fast, and maintainable website that's a joy to work with and performs excellently for users. Whether you're building your first website or your hundredth, these technologies and patterns provide a solid foundation for any web project.
← Back to Writing
Building This Site: A Modern Web Development Journey
8 min read
web-developmentnextjstypescripttailwindcloudflare
How I built my personal website using Next.js, TypeScript, and Tailwind CSS - from initial setup to production deployment on Cloudflare Pages.