
After years of wrestling with WordPress and its inevitable complexity creep, I decided to build my personal portfolio from scratch. What started as a simple "get my content back online" project evolved into a comprehensive exploration of modern web development practices. Here's the story of building a high-performance, fully tested portfolio that leverages the best of static site generation and edge computing.
WordPress served me well for years, but as any engineer knows, complexity has a way of accumulating. Plugin conflicts, security updates, database maintenance, and the constant battle against technical debt were consuming time I'd rather spend on content creation and actual engineering work.
The final straw came when I realized I was spending more time maintaining the CMS than actually writing. It was time for a change.
Before writing a single line of code, I established clear principles for the new site:
- Static site generation for zero server-side overhead
- Edge optimization through Cloudflare
- Aggressive caching and asset optimization
- Mobile-first responsive design
- TypeScript for type safety and better tooling
- 100% test coverage for confidence in changes
- Hot reloading and fast development cycles
- Clear separation of concerns
- Markdown-based content management
- No database dependencies
- Version-controlled content
- Simple blog post creation workflow
- Modern React patterns with hooks
- Component composition over inheritance
- Progressive enhancement
- Extensible design patterns
Next.js was the obvious choice for its excellent static site generation capabilities, built-in optimizations, and mature ecosystem. The ability to pre-render pages at build time while maintaining the flexibility of React components was exactly what I needed.
// next.config.js
module.exports = {
output: "export", // Static export for Cloudflare Pages
images: {
loader: "imgix",
path: "/",
},
webpack: (config, { isServer }) => {
if (isServer) {
require("./scripts/generate-sitemap.js");
}
return config;
},
};
TypeScript brings compile-time error checking and excellent IDE support. Every component, hook, and utility function is fully typed, making refactoring safe and development more predictable.
interface SectionConfig {
id: string;
navTitle: string;
backgroundColor: string;
contentFile: string;
backgroundVideo?: string;
enabled: boolean;
}
Cloudflare Pages provides excellent performance through global edge distribution, while Cloudflare Workers enable custom image optimization. The combination delivers sub-second load times globally.
Tailwind's utility-first approach keeps CSS maintainable while enabling rapid development. The built-in purging removes unused styles in production, keeping bundle sizes minimal.
The site uses a component composition pattern with clear separation of concerns:
// Polymorphic navigation component
<Navigation
variant="standard" // or "blogPost"
sections={sections}
SiteTitle={title}
/>
This approach allows the same component to handle different layouts while maintaining a single source of truth for navigation logic.
Content is managed through a hybrid approach:
- Main Sections: Markdown files in
/content/
directory - Blog Posts: Individual markdown files in
/posts/
with frontmatter - Configuration: JSON-based site configuration
- Navigation: Code-based configuration for type safety
---
title: 'My Blog Post'
date: '2024-07-03T15:30:00+12:00'
excerpt: 'Brief description for SEO'
tags: ['nextjs', 'typescript']
---
# Your Content Here
This approach provides the flexibility of a headless CMS while maintaining the simplicity of file-based management.
Instead of relying on Next.js image optimization (which doesn't work with static exports), I implemented a custom solution using Cloudflare Workers:
const cloudflareImageLoader = ({ src, width, quality }) => {
return `https://image-resize.yuvalararat.workers.dev?width=${width}&quality=${quality}&image=${process.env.site_address}${src}`
}
This provides:
- Automatic format selection (WebP/AVIF)
- Responsive sizing
- Quality optimization
- Global CDN delivery
PostCSS with PurgeCSS removes unused Tailwind classes:
// postcss.config.js
module.exports = {
plugins: [
'tailwindcss',
process.env.NODE_ENV === 'production' ?
['@fullhuman/postcss-purgecss', {
content: [
'./pages/**/*.{js,jsx,ts,tsx}',
'./components/**/*.{js,jsx,ts,tsx}',
],
}] : undefined,
],
}
This typically reduces CSS bundle size by 90%+ in production.
Every page is pre-rendered at build time, including dynamic blog post routes:
export async function getStaticPaths() {
const files = fs.readdirSync('posts')
const paths = files.map(filename => ({
params: { slug: filename.replace('.md', '') }
}))
return { paths, fallback: false }
}
One of the project's key requirements was comprehensive test coverage. This provides confidence when refactoring and ensures all edge cases are handled.
// Example component test
describe('BlogPost', () => {
test('should render with correct props', () => {
const { getByRole } = render(<BlogPost {...props} />)
const article = getByRole('article')
expect(article).toBeInTheDocument()
})
})
The test suite includes:
- Component unit tests
- Custom hook tests
- Utility function tests
- Page integration tests
- Accessibility tests
Jest with React Testing Library provides a solid foundation:
// jest.config.js
module.exports = {
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
}
This strict coverage requirement ensures no code paths are left untested.
The site is built mobile-first with progressive enhancement for larger screens. Touch targets meet accessibility guidelines, and the navigation adapts seamlessly across devices.
Core Web Vitals are a priority:
- LCP: Under 1.5s through image optimization and static generation
- FID: Minimal JavaScript and efficient event handlers
- CLS: Proper image dimensions and layout stability
WCAG 2.1 AA compliance through:
- Semantic HTML structure
- Proper heading hierarchy
- Keyboard navigation support
- Screen reader compatibility
- Sufficient color contrast ratios
The blog system supports:
- Full-text search across titles, descriptions, and tags
- Infinite scroll for performance
- Responsive card layout
- SEO-optimized individual post pages
Real-time search with debouncing provides instant results:
useEffect(() => {
const searchLower = searchTerm.toLowerCase();
const filtered = posts.filter(post => {
const title = post.frontMatter.title?.toLowerCase() || '';
const description = post.frontMatter.description?.toLowerCase() || '';
const tags = post.frontMatter.tags?.toLowerCase() || '';
return title.includes(searchLower) ||
description.includes(searchLower) ||
tags.includes(searchLower);
});
setFilteredPosts(filtered);
}, [searchTerm, posts]);
The deployment process is fully automated:
- Code push triggers build on Cloudflare Pages
- Sitemap generation during webpack build
- Static export to
/out
directory - Global distribution through Cloudflare edge network
Production security through Cloudflare headers:
Content-Security-Policy: default-src 'self'; ...
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
The upfront investment in TypeScript interfaces and proper typing paid off immediately during development. Refactoring became fearless, and bugs were caught at compile time rather than runtime.
The 100% coverage requirement forced better component design. Components became more focused and easier to test, leading to better overall architecture.
Users notice fast sites. The combination of static generation, edge distribution, and optimized assets creates a noticeably snappy experience.
The markdown-based content system might seem limiting compared to a full CMS, but it's actually liberating. Version control, easy backups, and fast builds more than compensate for the reduced complexity.
The results speak for themselves:
- Lighthouse Score: 100/100/100/100 (Performance/Accessibility/Best Practices/SEO)
- First Contentful Paint: < 0.8s
- Time to Interactive: < 1.2s
- Total Blocking Time: < 50ms
- Bundle Size: < 100KB gzipped
The architecture supports several planned enhancements:
Theme switching with localStorage persistence and system preference detection.
Fuzzy search, tag filtering, and search analytics.
Core Web Vitals tracking and performance regression detection.
Offline capability and background sync for better progressive web app features.
Building this portfolio from scratch was both a learning experience and a practical solution to content management fatigue. The combination of modern tooling, thoughtful architecture, and performance optimization created a platform that's both enjoyable to develop and fast for users.
The key takeaways:
- Static site generation provides unbeatable performance for content-focused sites
- TypeScript and testing create a foundation for confident development
- Edge computing through Cloudflare transforms the user experience
- Simple content management can be more powerful than complex CMSs
For engineers tired of CMS complexity, this approach offers a path back to fundamentals while embracing modern web development practices. The code is the documentation, the tests are the specification, and the performance speaks for itself.
The journey from WordPress to this custom solution wasn't just about technology choices—it was about reclaiming the joy of web development while delivering an exceptional user experience. Mission accomplished.
The complete source code and documentation for this project is available in the repository. Feel free to explore the implementation details and adapt the patterns for your own projects.