2025-07-21 by Remi Kristelijn
In this final post of our series, I'll show you how to optimize your Next.js blog code by applying the coding principles from rules.md. We'll refactor components, improve type safety, and ensure the code follows best practices for maintainability and clarity.
Our optimization follows these key principles:
Create comprehensive documentation for all components in src/components/README.md:
# Components Documentation This directory contains reusable UI components following the C4C (Coding for Clarity) principle. ## Navigation - **Purpose**: Consistent header navigation across pages - **Props**: `title`, `showHome`, `showBack`, `showBlogPosts` - **Usage**: Used on all pages for consistent navigation ## PostCard - **Purpose**: Display blog post preview cards - **Props**: `post` (Post interface) - **Usage**: Used in blog listing page ## PostContent - **Purpose**: Render individual blog post content - **Props**: `post` (Post interface) - **Usage**: Used in individual post pages ## ThemeRegistry - **Purpose**: Handle Material-UI theme and SSR compatibility - **Props**: `children` - **Usage**: Wraps the entire application ## ErrorBoundary - **Purpose**: Catch and handle React errors gracefully - **Props**: `children` - **Usage**: Wraps the entire application for error handling
Enhance src/types/index.ts with comprehensive type safety:
/** * Type definitions for the Next.js Blog application * * Following the NBI (Naming by Intention) principle, * all types are named to clearly indicate their purpose. */ // See src/types/index.ts for the actual type definitions export interface Post { id: string; title: string; excerpt: string; date: string; author: string; // Added in later updates slug: string; content: string; } export interface PostPageProps { params: Promise<{ slug: string }>; } export type PostsPageProps = Record<string, never>; export interface PostCardProps { post: Post; } export interface PostContentProps { post: Post; } export interface NavigationProps { title?: string; showHome?: boolean; showBack?: boolean; showBlogPosts?: boolean; }
Optimize src/lib/posts.ts following the HIPI principle:
/** * Posts data layer - handles all post-related data operations * * This module encapsulates all post data logic, making it easier to: * - Switch data sources (CMS, file system, API) * - Add caching and optimization * - Implement proper error handling * - Test data operations independently * * Uses MDX files from src/content/posts/ for blog content. * * MDX files should have the following frontmatter: * --- * title: "Post Title" * date: "YYYY-MM-DD" * excerpt: "Brief description" * --- * * The content follows the frontmatter in standard markdown format. */ import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; import type { Post } from '@/types'; const POSTS_DIRECTORY = path.join(process.cwd(), 'src/content/posts'); /** * Get all blog posts from MDX files * @returns Array of all posts with metadata */ export function getAllPosts(): Post[] { try { const fileNames = fs.readdirSync(POSTS_DIRECTORY); const mdxFiles = fileNames.filter(fileName => fileName.endsWith('.mdx')); const posts = mdxFiles.map(fileName => { const slug = fileName.replace(/\.mdx$/, ''); return getPostBySlug(slug); }).filter((post): post is Post => post !== undefined); // Sort posts by date (newest first) return posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); } catch (error) { console.error('Error reading posts directory:', error); return []; } } /** * Get a specific post by its slug * @param slug - The post slug to look up * @returns The post if found, undefined otherwise */ export function getPostBySlug(slug: string): Post | undefined { try { const fullPath = path.join(POSTS_DIRECTORY, `${slug}.mdx`); // Check if file exists if (!fs.existsSync(fullPath)) { return undefined; } // Read the MDX file const fileContents = fs.readFileSync(fullPath, 'utf8'); // Parse the frontmatter and content const { data, content } = matter(fileContents); // Validate required fields if (!data.title || !data.date || !data.excerpt) { console.warn(`Missing required frontmatter fields in ${slug}.mdx`); return undefined; } return { id: slug, slug, title: data.title, date: data.date, excerpt: data.excerpt, content }; } catch (error) { console.error(`Error reading post ${slug}:`, error); return undefined; } } /** * Get all post slugs (useful for static generation) * @returns Array of all post slugs */ export function getAllPostSlugs(): string[] { try { const fileNames = fs.readdirSync(POSTS_DIRECTORY); return fileNames .filter(fileName => fileName.endsWith('.mdx')) .map(fileName => fileName.replace(/\.mdx$/, '')); } catch (error) { console.error('Error reading posts directory:', error); return []; } } /** * Check if a post exists * @param slug - The post slug to check * @returns True if the post exists, false otherwise */ export function postExists(slug: string): boolean { const fullPath = path.join(POSTS_DIRECTORY, `${slug}.mdx`); return fs.existsSync(fullPath); }
Update src/app/page.tsx following the KISS principle:
import { Box } from '@mui/material'; import Header from '@/components/Header'; import Hero from '@/components/Hero'; import Features from '@/components/Features'; import Footer from '@/components/Footer'; /** * Home page - displays the main landing page * * This page follows the KISS principle by using simple, focused components * and the HIPI principle by hiding implementation details behind clean interfaces. */ export default function Home() { return ( <Box sx={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}> <Header title="Next.js Blog" showBlogPostsButton={true} /> <Hero /> <Features /> <Footer /> </Box> ); }
Update src/app/posts/page.tsx:
import { Container, Typography, Box } from '@mui/material'; import Navigation from '@/components/Navigation'; import PostCard from '@/components/PostCard'; import { getAllPosts } from '@/lib/posts'; /** * Posts listing page - displays all available blog posts * * This page follows the C4C principle by using clear, reusable components * and the HIPI principle by hiding implementation details behind clean interfaces. */ export default function PostsPage() { const posts = getAllPosts(); // Fetches posts from data layer return ( <Box sx={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}> <Navigation title="Blog Posts" showHome={true} showBack={false} /> <Container maxWidth="md" sx={{ flex: 1, py: 4 }}> <Typography variant="h3" component="h1" gutterBottom sx={{ mb: 4 }}> Blog Posts </Typography> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}> {posts.map((post) => ( <PostCard key={post.id} post={post} /> ))} </Box> </Container> </Box> ); }
Update src/app/posts/[slug]/page.tsx:
import { notFound } from 'next/navigation'; import { Container, Box } from '@mui/material'; import Navigation from '@/components/Navigation'; import PostContent from '@/components/PostContent'; import { getPostBySlug } from '@/lib/posts'; import type { PostPageProps } from '@/types'; /** * Individual blog post page - displays a single blog post * * This page follows the C4C principle by using clear, reusable components * and proper error handling. It also follows the HIPI principle by hiding * data fetching logic behind clean interfaces. */ export default async function PostPage({ params }: PostPageProps) { const { slug } = await params; const post = getPostBySlug(slug); // Fetches post from data layer if (!post) { notFound(); } return ( <Box sx={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}> <Navigation title={post.title} showHome={true} showBack={true} /> <Container maxWidth="md" sx={{ flex: 1, py: 4 }}> <PostContent post={post} /> </Container> </Box> ); }
Create src/content/README.md to document content structure:
# Content Structure This directory contains all blog content in MDX format. ## File Structure
src/content/ └── posts/ ├── 01-creating-nextjs-project.mdx ├── 02-github-actions-deployment.mdx ├── 03-adding-mdx-functionality.mdx ├── 04-integrating-material-ui.mdx └── 05-optimizing-code-quality.mdx
## MDX File Format Each MDX file should have the following frontmatter: ```markdown --- title: "Your Post Title" date: "YYYY-MM-DD" excerpt: "Brief description of your post" ---
.mdx file in src/content/posts/## Step 6: Remove Unused Dependencies Following the YAGNI principle, remove any unused dependencies: ```bash npm uninstall @next/mdx @mdx-js/loader @mdx-js/react
Since we're using gray-matter and react-markdown instead of MDX for simplicity.
Enhance error handling throughout the application:
'use client'; import React from 'react'; import { Box, Typography, Button } from '@mui/material'; interface ErrorBoundaryState { hasError: boolean; } /** * ErrorBoundary - catches and handles React errors gracefully * * This component follows the HIPI principle by providing a clean * error interface while hiding the complex error handling logic. */ export default class ErrorBoundary extends React.Component< { children: React.ReactNode }, ErrorBoundaryState > { constructor(props: { children: React.ReactNode }) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(): ErrorBoundaryState { return { hasError: true }; } componentDidCatch(error: unknown, errorInfo: unknown) { console.error('Error caught by boundary:', error, errorInfo); } render() { if (this.state.hasError) { return ( <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', p: 3, }} > <Typography variant="h4" gutterBottom> Something went wrong </Typography> <Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}> We're sorry, but something unexpected happened. </Typography> <Button variant="contained" onClick={() => window.location.reload()} > Reload Page </Button> </Box> ); } return this.props.children; } }
Update next.config.ts for optimal performance:
import type { NextConfig } from "next"; /** * Next.js configuration optimized for Cloudflare deployment * * This configuration follows the RTFM principle by using * Next.js conventions and the KISS principle by keeping * configuration simple and focused. */ const nextConfig: NextConfig = { output: "export", trailingSlash: true, images: { unoptimized: true }, // Optimize for performance experimental: { optimizeCss: true, }, // Security headers async headers() { return [ { source: '/(.*)', headers: [ { key: 'X-Frame-Options', value: 'DENY', }, { key: 'X-Content-Type-Options', value: 'nosniff', }, ], }, ]; }, }; export default nextConfig;
Create a simple performance monitoring utility:
// src/lib/performance.ts export function measurePerformance(name: string, fn: () => void) { const start = performance.now(); fn(); const end = performance.now(); console.log(`${name} took ${end - start} milliseconds`); }
Your Next.js blog is now optimized and ready for production! Consider these future enhancements:
Congratulations! You've successfully built and optimized a modern Next.js blog with excellent code quality. The application follows all the coding principles and is ready for production deployment.