← 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.

# 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.