2024-08-24 by Remi Kristelijn
After building our Next.js blog template and seeing it grow from a simple project to a community-driven platform, it's time to take a step back and analyze the code quality. This post documents a comprehensive quality audit, identifies areas for improvement, and implements best practices learned from production systems.
Our current codebase consists of:
1// Current approach - many custom overrides 2sx={{ 3 '& h1': { fontSize: '2rem', fontWeight: 700, mb: 2, mt: 3 }, 4 '& h2': { fontSize: '1.75rem', fontWeight: 600, mb: 1.5, mt: 2.5 }, 5 '& h3': { fontSize: '1.5rem', fontWeight: 600, mb: 1, mt: 2 }, 6 // ... many more overrides 7}} 8 9// Better approach - use theme tokens 10sx={{ 11 '& h1': (theme) => theme.typography.h1, 12 '& h2': (theme) => theme.typography.h2, 13 '& h3': (theme) => theme.typography.h3, 14}}
1// 125 lines of mixed concerns 2export default function Mermaid({ chart, id }: MermaidProps) { 3 // Theme configuration 4 // Rendering logic 5 // Error handling 6 // Styling 7 // All in one component 8}
1/** 2 * Hook for managing Mermaid configuration and rendering 3 * @param chart - Mermaid diagram syntax 4 * @param id - Optional unique identifier 5 * @returns Rendering state and error handling 6 */ 7export function useMermaidRenderer(chart: string, id?: string) { 8 // Configuration and rendering logic 9} 10 11/** 12 * Mermaid diagram component with theme integration 13 * @param props - Chart configuration and styling options 14 */ 15export default function Mermaid({ chart, id, className }: MermaidProps) { 16 const { elementRef, error, isLoading } = useMermaidRenderer(chart, id); 17 18 if (error) return <MermaidError error={error} />; 19 if (isLoading) return <MermaidSkeleton />; 20 21 return <MermaidContainer ref={elementRef} className={className} />; 22}
1/** 2 * Extended Material-UI theme with custom tokens 3 */ 4export const blogTheme = createTheme({ 5 // Standard MUI theme 6 palette: { 7 primary: { main: '#1976d2' }, 8 secondary: { main: '#dc004e' }, 9 }, 10 11 // Custom tokens for blog-specific styling 12 components: { 13 MuiTypography: { 14 styleOverrides: { 15 h1: ({ theme }) => ({ 16 fontSize: '2rem', 17 fontWeight: 700, 18 marginBottom: theme.spacing(2), 19 marginTop: theme.spacing(3), 20 }), 21 }, 22 }, 23 }, 24 25 // Custom spacing for consistent layouts 26 custom: { 27 layout: { 28 contentMaxWidth: '800px', 29 sidebarWidth: '300px', 30 }, 31 blog: { 32 postSpacing: 3, 33 cardElevation: 2, 34 }, 35 }, 36});
1/** 2 * Blog post content with standardized typography 3 */ 4export default function PostContent({ post }: PostContentProps) { 5 return ( 6 <Box sx={{ mb: 4 }}> 7 <Typography variant="h1" component="h1" gutterBottom> 8 {post.title} 9 </Typography> 10 11 <BlogMeta date={post.date} author={post.author} /> 12 13 <BlogContent> 14 <ReactMarkdown components={markdownComponents}> 15 {post.content} 16 </ReactMarkdown> 17 </BlogContent> 18 </Box> 19 ); 20}
1const features: Feature[] = [ 2 { 3 icon: 'π', 4 title: 'Fast', 5 description: 'Built with Next.js for optimal performance' 6 }, 7 // Hardcoded in component 8];
1/** 2 * Blog configuration and content data 3 */ 4export const blogConfig = { 5 features: [ 6 { 7 id: 'performance', 8 icon: 'π', 9 title: 'Fast Performance', 10 description: 'Built with Next.js 15 and optimized for speed', 11 metrics: { lighthouse: 95, coreWebVitals: 'good' }, 12 }, 13 ], 14 15 navigation: { 16 primary: [ 17 { label: 'Home', href: '/', icon: 'home' }, 18 { label: 'Posts', href: '/posts', icon: 'article' }, 19 ], 20 }, 21} as const; 22 23/** 24 * Type-safe configuration access 25 */ 26export type BlogConfig = typeof blogConfig; 27export type Feature = BlogConfig['features'][0];
1/** 2 * Enhanced error boundary with logging and recovery 3 */ 4export class BlogErrorBoundary extends Component< 5 ErrorBoundaryProps, 6 ErrorBoundaryState 7> { 8 constructor(props: ErrorBoundaryProps) { 9 super(props); 10 this.state = { hasError: false, error: null }; 11 } 12 13 static getDerivedStateFromError(error: Error): ErrorBoundaryState { 14 return { hasError: true, error }; 15 } 16 17 componentDidCatch(error: Error, errorInfo: ErrorInfo) { 18 // Log to monitoring service 19 this.logError(error, errorInfo); 20 21 // Report to analytics 22 this.reportError(error, errorInfo); 23 } 24 25 private logError = (error: Error, errorInfo: ErrorInfo) => { 26 if (process.env.NODE_ENV === 'development') { 27 console.error('Blog Error:', error, errorInfo); 28 } 29 30 // In production, send to monitoring service 31 // monitoringService.captureException(error, { extra: errorInfo }); 32 }; 33 34 render() { 35 if (this.state.hasError) { 36 return ( 37 <ErrorFallback 38 error={this.state.error} 39 onRetry={() => this.setState({ hasError: false, error: null })} 40 /> 41 ); 42 } 43 44 return this.props.children; 45 } 46}
1/** 2 * Hook for managing blog post data with caching 3 */ 4export function useBlogPosts() { 5 const [posts, setPosts] = useState<Post[]>([]); 6 const [loading, setLoading] = useState(true); 7 const [error, setError] = useState<Error | null>(null); 8 9 useEffect(() => { 10 const loadPosts = async () => { 11 try { 12 setLoading(true); 13 const postsData = await getAllPosts(); 14 setPosts(postsData); 15 } catch (err) { 16 setError(err instanceof Error ? err : new Error('Failed to load posts')); 17 } finally { 18 setLoading(false); 19 } 20 }; 21 22 loadPosts(); 23 }, []); 24 25 return { posts, loading, error, refetch: () => loadPosts() }; 26} 27 28/** 29 * Hook for theme-aware styling 30 */ 31export function useThemeStyles() { 32 const theme = useTheme(); 33 34 return useMemo(() => ({ 35 contentContainer: { 36 maxWidth: theme.custom.layout.contentMaxWidth, 37 margin: '0 auto', 38 padding: theme.spacing(0, 2), 39 }, 40 41 blogCard: { 42 elevation: theme.custom.blog.cardElevation, 43 spacing: theme.custom.blog.postSpacing, 44 }, 45 }), [theme]); 46}
1// src/test-utils/setup.ts 2import '@testing-library/jest-dom'; 3import { render, RenderOptions } from '@testing-library/react'; 4import { ThemeProvider } from '@mui/material/styles'; 5import { blogTheme } from '../lib/theme'; 6 7/** 8 * Custom render function with theme provider 9 */ 10export function renderWithTheme( 11 ui: React.ReactElement, 12 options?: RenderOptions 13) { 14 const Wrapper = ({ children }: { children: React.ReactNode }) => ( 15 <ThemeProvider theme={blogTheme}> 16 {children} 17 </ThemeProvider> 18 ); 19 20 return render(ui, { wrapper: Wrapper, ...options }); 21} 22 23export * from '@testing-library/react'; 24export { renderWithTheme as render };
1// src/components/__tests__/PostCard.test.tsx 2import { render, screen } from '../../test-utils/setup'; 3import PostCard from '../PostCard'; 4import { mockPost } from '../../test-utils/mocks'; 5 6describe('PostCard', () => { 7 it('renders post information correctly', () => { 8 render(<PostCard post={mockPost} />); 9 10 expect(screen.getByText(mockPost.title)).toBeInTheDocument(); 11 expect(screen.getByText(mockPost.excerpt)).toBeInTheDocument(); 12 expect(screen.getByText(mockPost.date)).toBeInTheDocument(); 13 }); 14 15 it('navigates to post when clicked', () => { 16 const { container } = render(<PostCard post={mockPost} />); 17 const link = container.querySelector('a'); 18 19 expect(link).toHaveAttribute('href', `/posts/${mockPost.slug}`); 20 }); 21 22 it('handles missing optional fields gracefully', () => { 23 const postWithoutAuthor = { ...mockPost, author: undefined }; 24 25 expect(() => { 26 render(<PostCard post={postWithoutAuthor} />); 27 }).not.toThrow(); 28 }); 29});
1// src/hooks/__tests__/useBlogPosts.test.ts 2import { renderHook, waitFor } from '@testing-library/react'; 3import { useBlogPosts } from '../useBlogPosts'; 4import * as postsLib from '../../lib/posts'; 5 6jest.mock('../../lib/posts'); 7const mockGetAllPosts = postsLib.getAllPosts as jest.MockedFunction<typeof postsLib.getAllPosts>; 8 9describe('useBlogPosts', () => { 10 beforeEach(() => { 11 jest.clearAllMocks(); 12 }); 13 14 it('loads posts successfully', async () => { 15 const mockPosts = [{ id: '1', title: 'Test Post' }]; 16 mockGetAllPosts.mockResolvedValue(mockPosts); 17 18 const { result } = renderHook(() => useBlogPosts()); 19 20 expect(result.current.loading).toBe(true); 21 22 await waitFor(() => { 23 expect(result.current.loading).toBe(false); 24 }); 25 26 expect(result.current.posts).toEqual(mockPosts); 27 expect(result.current.error).toBeNull(); 28 }); 29 30 it('handles errors gracefully', async () => { 31 const error = new Error('Failed to load'); 32 mockGetAllPosts.mockRejectedValue(error); 33 34 const { result } = renderHook(() => useBlogPosts()); 35 36 await waitFor(() => { 37 expect(result.current.loading).toBe(false); 38 }); 39 40 expect(result.current.error).toEqual(error); 41 expect(result.current.posts).toEqual([]); 42 }); 43});
1/** 2 * Memoized post card for list performance 3 */ 4export const PostCard = memo(function PostCard({ post }: PostCardProps) { 5 const styles = useThemeStyles(); 6 7 return ( 8 <Card sx={styles.blogCard}> 9 <CardContent> 10 <Typography variant="h5" component="h2"> 11 {post.title} 12 </Typography> 13 <Typography variant="body2" color="text.secondary"> 14 {post.excerpt} 15 </Typography> 16 </CardContent> 17 </Card> 18 ); 19});
1/** 2 * Lazy-loaded Mermaid component for better initial load 3 */ 4const Mermaid = lazy(() => import('./Mermaid')); 5 6export function LazyMermaid(props: MermaidProps) { 7 return ( 8 <Suspense fallback={<MermaidSkeleton />}> 9 <Mermaid {...props} /> 10 </Suspense> 11 ); 12}
1// webpack-bundle-analyzer integration 2const withBundleAnalyzer = require('@next/bundle-analyzer')({ 3 enabled: process.env.ANALYZE === 'true', 4}); 5 6module.exports = withBundleAnalyzer({ 7 // Next.js config 8});
src/
βββ components/
β βββ ui/ # Reusable UI components
β βββ blog/ # Blog-specific components
β βββ __tests__/ # Component tests
βββ hooks/ # Custom React hooks
βββ lib/ # Utility functions
βββ types/ # TypeScript definitions
βββ config/ # Configuration files
βββ test-utils/ # Testing utilities
1/** 2 * Strict type definitions with proper documentation 3 */ 4export interface Post { 5 /** Unique identifier for the post */ 6 readonly id: string; 7 8 /** SEO-friendly URL slug */ 9 readonly slug: string; 10 11 /** Post title for display and SEO */ 12 title: string; 13 14 /** Brief description for previews and SEO */ 15 excerpt: string; 16 17 /** Full post content in markdown format */ 18 content: string; 19 20 /** Publication date in ISO format */ 21 date: string; 22 23 /** Optional author information */ 24 author?: string; 25 26 /** Optional featured image URL */ 27 featuredImage?: string; 28} 29 30/** 31 * Type-safe post operations 32 */ 33export type PostOperations = { 34 readonly getAllPosts: () => Promise<readonly Post[]>; 35 readonly getPostBySlug: (slug: string) => Promise<Post | null>; 36 readonly searchPosts: (query: string) => Promise<readonly Post[]>; 37};
1/** 2 * Result type for operations that can fail 3 */ 4export type Result<T, E = Error> = 5 | { success: true; data: T } 6 | { success: false; error: E }; 7 8/** 9 * Safe post loading with proper error handling 10 */ 11export async function safeGetAllPosts(): Promise<Result<Post[]>> { 12 try { 13 const posts = await getAllPosts(); 14 return { success: true, data: posts }; 15 } catch (error) { 16 return { 17 success: false, 18 error: error instanceof Error ? error : new Error('Unknown error') 19 }; 20 } 21}
1/** 2 * Performance monitoring for blog operations 3 */ 4export function withPerformanceMonitoring<T extends (...args: any[]) => any>( 5 fn: T, 6 operationName: string 7): T { 8 return ((...args: Parameters<T>) => { 9 const start = performance.now(); 10 11 try { 12 const result = fn(...args); 13 14 if (result instanceof Promise) { 15 return result.finally(() => { 16 const duration = performance.now() - start; 17 logPerformance(operationName, duration); 18 }); 19 } 20 21 const duration = performance.now() - start; 22 logPerformance(operationName, duration); 23 return result; 24 } catch (error) { 25 const duration = performance.now() - start; 26 logError(operationName, error, duration); 27 throw error; 28 } 29 }) as T; 30}
1/** 2 * Privacy-focused analytics for blog engagement 3 */ 4export const analytics = { 5 trackPageView: (path: string) => { 6 // Track page views without personal data 7 if (typeof window !== 'undefined' && process.env.NODE_ENV === 'production') { 8 // Send to analytics service 9 } 10 }, 11 12 trackPostRead: (postId: string, readTime: number) => { 13 // Track reading engagement 14 }, 15 16 trackError: (error: Error, context: string) => { 17 // Track errors for improvement 18 }, 19};
This code quality analysis transformed our blog template from a functional prototype into a production-ready platform. By implementing proper separation of concerns, comprehensive testing, performance monitoring, and following TypeScript best practices, we've created a maintainable and scalable codebase.
The improvements resulted in:
Quality code isn't just about following rulesβit's about creating a foundation that enables rapid development, easy maintenance, and confident deployments. These practices ensure our blog template can continue to evolve and serve the community effectively.
Quality is not an act, it is a habit. - Aristotle