The Results of Cutting Out The Bloat
A while back, I wrote about cutting out the bloat in web development. The core idea: stop reaching for React/Vue/Next.js for everything and use server-side rendering where it makes sense. Now that this blog has been running for a while, I wanted to share the actual results of that philosophy - including what complexity I kept and what I avoided. Spoiler alert: the results are excellent.
Setting the Record Straight
Before diving into metrics, let me be clear about what "cutting the bloat" actually means for this site. I'm not running some minimalist setup with zero tooling. In fact, the infrastructure is fairly sophisticated:
- CI/CD Pipeline: GitHub Actions with parallel test execution, security scanning, linting, type checking, and multi-service deployment
- Build Process: PostCSS, PurgeCSS, JavaScript minification with Terser, image optimization, and Brotli compression
- Infrastructure: Four Docker containers (web, Celery worker, Celery Beat scheduler, Flower monitoring), deployed to CapRover
- Backend Services: Django, PostgreSQL, Redis, Playwright for server-side screenshots
- Testing: Comprehensive test suite with Docker Compose, parallel execution, and coverage reporting
So what did I actually cut out? The frontend framework bloat. No React. No Vue. No 3MB JavaScript bundle. No client-side routing. No hydration. Just server-rendered HTML with targeted JavaScript where needed.
This is an important distinction: sophisticated backend infrastructure for optimization and reliability is good. Shipping megabytes of JavaScript to render blog posts is not.
The Numbers Don't Lie
With automated nightly Lighthouse audits tracking performance over time, this site consistently scores in the high 90s across all four metrics:
- Performance: 95-100
- Accessibility: 95-100
- Best Practices: 95-100
- SEO: 95-100
For context, the average website scores around 50-70 on performance. Many popular blogging platforms struggle to break 80. This Django site with server-side rendering and smart optimization regularly hits perfect or near-perfect scores.
You can view the complete Lighthouse audit history to see these metrics tracked over the past 30 days, including graphs showing consistency and trends.
But scores are just numbers. What do they actually mean in the real world?
What Users Actually Experience
The performance metrics translate to real benefits:
- First Contentful Paint: Under 0.5 seconds - Users see content almost instantly
- Time to Interactive: Under 1 second - The page is fully usable immediately
- Total Page Size: Under 200KB for most pages - Uses less data than a single high-res image on most modern sites
- Zero JavaScript frameworks: No React, no Vue, no Next.js bloat to download and parse
Compare this to the typical blog built with modern frameworks: 2-3MB of JavaScript, 3-5 second load times, and a spinning loader while the framework bootstraps. Is that really necessary to display text and images?
The Developer Experience: Where Complexity Belongs
Here's the interesting part: building without frontend framework bloat doesn't mean avoiding all complexity. It means putting complexity where it provides value.
Build Pipeline: Sophisticated but Fast
My CSS build process uses PostCSS, PurgeCSS for removing unused styles, automatic minification, and generates both Gzip and Brotli compressed versions. The result? CSS that's heavily optimized with features like automatic prefixing and color optimization.
The difference from a typical Next.js build? This takes seconds, not minutes. There's no webpack configuration hell, no fighting with module resolution, no cache invalidation mysteries. Just run make static and it works.
CI/CD: Comprehensive and Reliable
My CI/CD pipeline is actually quite complex:
- Parallel test execution across multiple test groups
- Security scanning with Safety and pip-audit
- Code quality checks: Ruff, Black, isort, Pylint, MyPy
- Docker image builds for four different services
- Automated deployment to CapRover with health checks
- Coverage reporting and tracking
This runs on every push. Tests complete in minutes, not the 10-20 minutes I've seen in typical React projects.
The key difference? I'm testing Python code and database interactions, not waiting for TypeScript to compile and Jest to instrument thousands of JavaScript files.
Deployment: Multiple Services, Simple Orchestration
The production environment runs four separate Docker containers:
- Web: Gunicorn serving Django
- Celery: Background task processing
- Celery Beat: Scheduled tasks (nightly Lighthouse audits, cache rebuilds)
- Flower: Real-time Celery monitoring
Plus PostgreSQL, Redis, and S3 for storage. This is not a "simple" architecture. But it's reliable, scalable, and maintainable because each service has a clear purpose and the deployment is automated.
Compare this to managing serverless functions, edge middleware, API routes, and client components in a Next.js app. More moving parts, more vendor lock-in, more debugging hell.
Debugging: Clear Stack Traces
When something breaks (and it will), debugging is straightforward. Django gives me clear stack traces pointing directly to the problem. No transpiled code, no framework magic, no "this error occurred during server-side rendering but actually happened on the client after hydration."
The CI/CD logs are equally clear. When a test fails, I see exactly which test and why, not cryptic webpack errors about module resolution.
What I Actually Sacrificed
Let's be honest - there are trade-offs. Here's what I gave up by avoiding the modern framework ecosystem:
- Complex client-side interactions: No infinite scroll, no optimistic updates, no complex state management
- Component reusability across projects: I use Django templates, not portable React components
- Hot module replacement: I refresh the browser manually (takes 0.2 seconds)
- Rich ecosystem of UI libraries: No shadcn/ui or Material UI - I write CSS
But here's the thing: I don't actually need any of that for a blog. Users don't care if I'm using the latest framework. They care if the site loads fast, looks good, and the content is easy to read.
Where I DO Use Build Tools (And Why)
I'm not anti-tooling. I use build tools where they provide clear value:
- PostCSS: Automatic vendor prefixing and optimizations
- PurgeCSS: Removes unused CSS (37% size reduction)
- Terser: JavaScript minification
- Brotli: Better compression than Gzip alone
- Docker: Consistent environments from development to production
- Playwright: Server-side screenshot generation for the knowledge graph
These tools solve real problems and don't add framework complexity. The entire build pipeline is defined in a Makefile and a few management commands. No package.json with 200 dependencies.
Maintenance and Technical Debt
This is where the benefits really compound over time. Every dependency you add is technical debt. Every framework version is a ticking time bomb of breaking changes.
With this approach:
- Fewer dependencies to update: I have 47 Python packages, not 1,000+ npm packages
- Standard technologies: HTML, CSS, Python, and PostgreSQL aren't going anywhere
- No framework churn: I'm not rewriting everything every 2 years because the ecosystem moved on
- Easier onboarding: Any developer who knows Django can contribute
- Clear upgrade paths: Django has excellent backwards compatibility and upgrade guides
I spend my time writing content and adding features, not fighting with ESLint configurations and webpack optimization plugins.
The Psychological Benefits
There's something incredibly liberating about working without framework bloat. I don't have decision fatigue about which state management library to use. I don't worry about client/server boundary issues. I don't spend hours troubleshooting "works on my machine" build problems.
When I want to add a feature, I:
- Write a Django view
- Create a template
- Write some tests
- Push to GitHub
- CI/CD handles the rest
No webpack configuration. No "use client" directives. No hydration errors. Just code that runs on the server and sends HTML to the browser.
This mental bandwidth goes into writing better content, designing better features, and actually shipping things.
When You DO Need the Frameworks
Let me be absolutely clear: frameworks aren't evil. They solve real problems for real applications. If you're building:
- A complex single-page application with lots of client-side state
- Real-time collaborative tools (think Figma or Google Docs)
- Data-heavy dashboards with complex visualizations
- Anything that genuinely needs sophisticated client-side logic
Then yes, use React or Vue or whatever. The frameworks exist for good reasons.
But if you're building a blog, a documentation site, a portfolio, or any primarily content-focused site? You probably don't need them. And you'll be better off without them.
Lessons Learned
After months of running this stack in production, here are my key takeaways:
- Backend complexity for optimization is good: Build tools, CI/CD, and infrastructure automation provide real value
- Frontend complexity for rendering content is bad: Shipping React to display blog posts is wasteful
- Performance is a feature users notice: Even if they don't consciously realize why, fast sites feel better
- Simplicity scales: As this blog grows, the architecture continues to work well
- Developer happiness matters: Working without framework bloat is more enjoyable
- Boring technology works: Django, PostgreSQL, and Redis are proven, reliable, and maintainable
The Bigger Picture
This isn't just about my blog. It's about a broader shift happening in web development. More developers are realizing that the "everything in JavaScript" approach has costs that often outweigh the benefits.
Projects like HTMX and Alpine.js show that you can have interactivity without framework overhead. The resurgence of server-side rendering (even in Next.js!) proves that rendering on the server was a good idea all along. DHH's writings on modern web development are resonating because people are tired of the complexity treadmill.
We built great websites before React existed. We can still build great websites without needing a framework for everything - especially when we have sophisticated build tools, testing, and deployment automation in place.
Conclusion
Cutting out frontend framework bloat delivered real, measurable benefits. The site is faster for users, easier to maintain, simpler to debug, and more enjoyable to work on.
But I didn't sacrifice professional engineering practices. I have a robust CI/CD pipeline, comprehensive testing, automated deployments, and sophisticated build optimization. The difference is that this complexity serves users and developers, not frameworks.
Could I have achieved similar results with Next.js or Remix? Maybe. But it would have required more expertise, more tooling, more debugging, and more ongoing maintenance. The server-side rendering approach just works, and it keeps working without constant attention.
As I mentioned in this blog framework ain't perfect, there are still rough edges and things I want to improve. But the foundation is solid because it's built on boring, proven technology with smart optimization where it counts.
So if you're starting a new project, ask yourself: do you really need that frontend framework? Or are you reaching for complexity out of habit? Sometimes the best solution is server-side rendering with great infrastructure, not client-side rendering with minimal tooling.
And if you don't believe me, just look at the Lighthouse scores. The numbers speak for themselves.
Comments (0)
Leave a Comment
You can comment anonymously or login to use your account.
No comments yet. Be the first to share your thoughts!