{"format_version": "agent-blog-post-v2", "title": "0007 The Results Of Cutting Out The Bloat", "category": "tech", "template_name": "0007_The_Results_Of_Cutting_Out_The_Bloat", "post_number": "0007", "canonical_url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/", "html_url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/", "markdown_url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/index.md", "json_url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/index.json", "published_at": "2026-03-26T17:04:55+00:00", "updated_at": "2026-07-08T16:59:22.396495+00:00", "ai_written": false, "summary": "Problem: 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. Approach: But the foundation is solid because it's built on boring, proven technology with smart optimization where it counts.", "takeaways": ["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.", "Just server-rendered HTML with targeted JavaScript where needed.", "This Django site with server-side rendering and smart optimization regularly hits perfect or near-perfect scores.", "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...", "Here's the interesting part: building without frontend framework bloat doesn't mean avoiding all complexity.", "My CSS build process uses PostCSS, PurgeCSS for removing unused styles, automatic minification, and generates both Gzip and Brotli compressed versions."], "questions": ["What problem does 0007 The Results Of Cutting Out The Bloat address?", "What position does Aaron take in 0007 The Results Of Cutting Out The Bloat?", "How does 0007 The Results Of Cutting Out The Bloat approach the problem?", "What evidence or outcomes does 0007 The Results Of Cutting Out The Bloat provide?", "What does the article explain about The Results of Cutting Out The Bloat?", "What does the article explain about Setting the Record Straight?"], "takeaway_sources": [{"text": "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.", "section": "The Results of Cutting Out The Bloat", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "Just server-rendered HTML with targeted JavaScript where needed.", "section": "Setting the Record Straight", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "This Django site with server-side rendering and smart optimization regularly hits perfect or near-perfect scores.", "section": "The Numbers Don't Lie", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "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...", "section": "What Users Actually Experience", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "Here's the interesting part: building without frontend framework bloat doesn't mean avoiding all complexity.", "section": "The Developer Experience: Where Complexity Belongs", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "My CSS build process uses PostCSS, PurgeCSS for removing unused styles, automatic minification, and generates both Gzip and Brotli compressed versions.", "section": "Build Pipeline: Sophisticated but Fast", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}], "agent_digest": {"problem": "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.", "aaron_position": "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.", "approach": "But the foundation is solid because it's built on boring, proven technology with smart optimization where it counts.", "outcome": "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.", "audience_fit": "Best for readers and agents researching Aaron Spindler's tech writing on 0007 The Results Of Cutting Out The Bloat.", "why_it_matters": "The Results of Cutting Out The Bloat A while back, I wrote about cutting out the bloat in web development.", "best_for": ["Understanding the main argument of 0007 The Results Of Cutting Out The Bloat.", "Finding citable details from Aaron's tech writing.", "Answering questions about The Results of Cutting Out The Bloat.", "Answering questions about Setting the Record Straight.", "Answering questions about The Numbers Don't Lie."], "not_for": ["Authoritative third-party documentation.", "A complete substitute for the canonical article body."], "core_claims": [{"text": "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.", "section": "The Results of Cutting Out The Bloat", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "Just server-rendered HTML with targeted JavaScript where needed.", "section": "Setting the Record Straight", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "This Django site with server-side rendering and smart optimization regularly hits perfect or near-perfect scores.", "section": "The Numbers Don't Lie", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}], "evidence": [{"text": "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.", "section": "The Results of Cutting Out The Bloat", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "Spoiler alert: the results are excellent.", "section": "The Results of Cutting Out The Bloat", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "Before diving into metrics, let me be clear about what \"cutting the bloat\" actually means for this site.", "section": "Setting the Record Straight", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "I'm not running some minimalist setup with zero tooling.", "section": "Setting the Record Straight", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}], "prerequisites": ["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.", "Just server-rendered HTML with targeted JavaScript where needed.", "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..."], "caveats": [{"text": "But it's reliable, scalable, and maintainable because each service has a clear purpose and the deployment is automated.", "section": "Deployment: Multiple Services, Simple Orchestration", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "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.", "section": "Debugging: Clear Stack Traces", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "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...", "section": "What I Actually Sacrificed", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}, {"text": "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...", "section": "Maintenance and Technical Debt", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/"}], "citation_targets": [{"label": "The Results of Cutting Out The Bloat", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/", "reason": "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."}, {"label": "Setting the Record Straight", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/", "reason": "Just server-rendered HTML with targeted JavaScript where needed."}, {"label": "The Numbers Don't Lie", "anchor": "", "url": "https://aaronspindler.com/b/tech/0007_The_Results_Of_Cutting_Out_The_Bloat/", "reason": "This Django site with server-side rendering and smart optimization regularly hits perfect or near-perfect scores."}]}, "agent_queries": ["Aaron Spindler \"0007 The Results Of Cutting Out The Bloat\"", "0007 The Results Of Cutting Out The Bloat tech Aaron Spindler", "0007 The Results Of Cutting Out The Bloat 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.", "0007 The Results Of Cutting Out The Bloat The Results of Cutting Out The Bloat", "0007 The Results Of Cutting Out The Bloat Setting the Record Straight"], "follow_up_questions": ["What changed after the approach in 0007 The Results Of Cutting Out The Bloat was applied?", "What tradeoffs or constraints remain after 0007 The Results Of Cutting Out The Bloat?", "What setup is required before applying the ideas in 0007 The Results Of Cutting Out The Bloat?", "How would The Results of Cutting Out The Bloat change in a different environment?"], "outline": [{"level": 1, "title": "The Results of Cutting Out The Bloat", "anchor": ""}, {"level": 2, "title": "Setting the Record Straight", "anchor": ""}, {"level": 2, "title": "The Numbers Don't Lie", "anchor": ""}, {"level": 2, "title": "What Users Actually Experience", "anchor": ""}, {"level": 2, "title": "The Developer Experience: Where Complexity Belongs", "anchor": ""}, {"level": 3, "title": "Build Pipeline: Sophisticated but Fast", "anchor": ""}, {"level": 3, "title": "CI/CD: Comprehensive and Reliable", "anchor": ""}, {"level": 3, "title": "Deployment: Multiple Services, Simple Orchestration", "anchor": ""}, {"level": 3, "title": "Debugging: Clear Stack Traces", "anchor": ""}, {"level": 2, "title": "What I Actually Sacrificed", "anchor": ""}, {"level": 2, "title": "Where I DO Use Build Tools (And Why)", "anchor": ""}, {"level": 2, "title": "Maintenance and Technical Debt", "anchor": ""}, {"level": 2, "title": "The Psychological Benefits", "anchor": ""}, {"level": 2, "title": "When You DO Need the Frameworks", "anchor": ""}, {"level": 2, "title": "Lessons Learned", "anchor": ""}, {"level": 2, "title": "The Bigger Picture", "anchor": ""}, {"level": 2, "title": "Conclusion", "anchor": ""}], "links": [{"text": "cutting out the bloat", "url": "https://aaronspindler.com/b/0002_cut_out_the_bloat/", "kind": "internal"}, {"text": "automated nightly Lighthouse audits", "url": "https://aaronspindler.com/lighthouse/history/", "kind": "internal"}, {"text": "view the complete Lighthouse audit history", "url": "https://aaronspindler.com/lighthouse/history/", "kind": "internal"}, {"text": "writings on modern web development", "url": "https://world.hey.com/dhh", "kind": "external"}, {"text": "this blog framework ain't perfect", "url": "https://aaronspindler.com/b/0004_this_blog_aint_perfect/", "kind": "internal"}], "related_posts": [{"title": "0002 Cut Out The Bloat", "category": "tech", "url": "https://aaronspindler.com/b/tech/0002_Cut_Out_The_Bloat/", "markdown_url": "https://aaronspindler.com/b/tech/0002_Cut_Out_The_Bloat/index.md", "json_url": "https://aaronspindler.com/b/tech/0002_Cut_Out_The_Bloat/index.json"}, {"title": "0004 This Blog Aint Perfect", "category": "personal", "url": "https://aaronspindler.com/b/personal/0004_This_Blog_Aint_Perfect/", "markdown_url": "https://aaronspindler.com/b/personal/0004_This_Blog_Aint_Perfect/index.md", "json_url": "https://aaronspindler.com/b/personal/0004_This_Blog_Aint_Perfect/index.json"}], "code_blocks": [], "plain_text": "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 Dokploy 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 Dokploy 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.", "body_markdown": "# The Results of Cutting Out The Bloat\n\nA while back, I wrote about [cutting out the bloat](https://aaronspindler.com/b/0002_cut_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.\n\n## Setting the Record Straight\n\nBefore 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:\n\n- **CI/CD Pipeline:** GitHub Actions with parallel test execution, security scanning, linting, type checking, and multi-service deployment\n- **Build Process:** PostCSS, PurgeCSS, JavaScript minification with Terser, image optimization, and Brotli compression\n- **Infrastructure:** Four Docker containers (web, Celery worker, Celery Beat scheduler, Flower monitoring), deployed to Dokploy\n- **Backend Services:** Django, PostgreSQL, Redis, Playwright for server-side screenshots\n- **Testing:** Comprehensive test suite with Docker Compose, parallel execution, and coverage reporting\n\nSo 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.\n\nThis is an important distinction: sophisticated backend infrastructure for optimization and reliability is good. Shipping megabytes of JavaScript to render blog posts is not.\n\n## The Numbers Don't Lie\n\nWith [automated nightly Lighthouse audits](https://aaronspindler.com/lighthouse/history/) tracking performance over time, this site consistently scores in the high 90s across all four metrics:\n\n- **Performance:** 95-100\n- **Accessibility:** 95-100\n- **Best Practices:** 95-100\n- **SEO:** 95-100\n\nFor 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.\n\nYou can [view the complete Lighthouse audit history](https://aaronspindler.com/lighthouse/history/) to see these metrics tracked over the past 30 days, including graphs showing consistency and trends.\n\nBut scores are just numbers. What do they actually mean in the real world?\n\n## What Users Actually Experience\n\nThe performance metrics translate to real benefits:\n\n- **First Contentful Paint:** Under 0.5 seconds - Users see content almost instantly\n- **Time to Interactive:** Under 1 second - The page is fully usable immediately\n- **Total Page Size:** Under 200KB for most pages - Uses less data than a single high-res image on most modern sites\n- **Zero JavaScript frameworks:** No React, no Vue, no Next.js bloat to download and parse\n\nCompare 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?\n\n## The Developer Experience: Where Complexity Belongs\n\nHere's the interesting part: building without frontend framework bloat doesn't mean avoiding all complexity. It means putting complexity where it provides value.\n\n### Build Pipeline: Sophisticated but Fast\n\nMy 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.\n\nThe 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.\n\n### CI/CD: Comprehensive and Reliable\n\nMy CI/CD pipeline is actually quite complex:\n\n- Parallel test execution across multiple test groups\n- Security scanning with Safety and pip-audit\n- Code quality checks: Ruff, Black, isort, Pylint, MyPy\n- Docker image builds for four different services\n- Automated deployment to Dokploy with health checks\n- Coverage reporting and tracking\n\nThis runs on every push. Tests complete in minutes, not the 10-20 minutes I've seen in typical React projects.\n\nThe key difference? I'm testing Python code and database interactions, not waiting for TypeScript to compile and Jest to instrument thousands of JavaScript files.\n\n### Deployment: Multiple Services, Simple Orchestration\n\nThe production environment runs four separate Docker containers:\n\n- **Web:** Gunicorn serving Django\n- **Celery:** Background task processing\n- **Celery Beat:** Scheduled tasks (nightly Lighthouse audits, cache rebuilds)\n- **Flower:** Real-time Celery monitoring\n\nPlus 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.\n\nCompare 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.\n\n### Debugging: Clear Stack Traces\n\nWhen 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.\"\n\nThe CI/CD logs are equally clear. When a test fails, I see exactly which test and why, not cryptic webpack errors about module resolution.\n\n## What I Actually Sacrificed\n\nLet's be honest - there are trade-offs. Here's what I gave up by avoiding the modern framework ecosystem:\n\n- **Complex client-side interactions:** No infinite scroll, no optimistic updates, no complex state management\n- **Component reusability across projects:** I use Django templates, not portable React components\n- **Hot module replacement:** I refresh the browser manually (takes 0.2 seconds)\n- **Rich ecosystem of UI libraries:** No shadcn/ui or Material UI - I write CSS\n\nBut 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.\n\n## Where I DO Use Build Tools (And Why)\n\nI'm not anti-tooling. I use build tools where they provide clear value:\n\n- **PostCSS:** Automatic vendor prefixing and optimizations\n- **PurgeCSS:** Removes unused CSS (37% size reduction)\n- **Terser:** JavaScript minification\n- **Brotli:** Better compression than Gzip alone\n- **Docker:** Consistent environments from development to production\n- **Playwright:** Server-side screenshot generation for the knowledge graph\n\nThese 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.\n\n## Maintenance and Technical Debt\n\nThis 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.\n\nWith this approach:\n\n- **Fewer dependencies to update:** I have 47 Python packages, not 1,000+ npm packages\n- **Standard technologies:** HTML, CSS, Python, and PostgreSQL aren't going anywhere\n- **No framework churn:** I'm not rewriting everything every 2 years because the ecosystem moved on\n- **Easier onboarding:** Any developer who knows Django can contribute\n- **Clear upgrade paths:** Django has excellent backwards compatibility and upgrade guides\n\nI spend my time writing content and adding features, not fighting with ESLint configurations and webpack optimization plugins.\n\n## The Psychological Benefits\n\nThere'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.\n\nWhen I want to add a feature, I:\n\n1. Write a Django view\n2. Create a template\n3. Write some tests\n4. Push to GitHub\n5. CI/CD handles the rest\n\nNo webpack configuration. No \"use client\" directives. No hydration errors. Just code that runs on the server and sends HTML to the browser.\n\nThis mental bandwidth goes into writing better content, designing better features, and actually shipping things.\n\n## When You DO Need the Frameworks\n\nLet me be absolutely clear: frameworks aren't evil. They solve real problems for real applications. If you're building:\n\n- A complex single-page application with lots of client-side state\n- Real-time collaborative tools (think Figma or Google Docs)\n- Data-heavy dashboards with complex visualizations\n- Anything that genuinely needs sophisticated client-side logic\n\nThen yes, use React or Vue or whatever. The frameworks exist for good reasons.\n\nBut 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.\n\n## Lessons Learned\n\nAfter months of running this stack in production, here are my key takeaways:\n\n- **Backend complexity for optimization is good:** Build tools, CI/CD, and infrastructure automation provide real value\n- **Frontend complexity for rendering content is bad:** Shipping React to display blog posts is wasteful\n- **Performance is a feature users notice:** Even if they don't consciously realize why, fast sites feel better\n- **Simplicity scales:** As this blog grows, the architecture continues to work well\n- **Developer happiness matters:** Working without framework bloat is more enjoyable\n- **Boring technology works:** Django, PostgreSQL, and Redis are proven, reliable, and maintainable\n\n## The Bigger Picture\n\nThis 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.\n\nProjects 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](https://world.hey.com/dhh) are resonating because people are tired of the complexity treadmill.\n\nWe 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.\n\n## Conclusion\n\nCutting 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.\n\nBut 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.\n\nCould 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.\n\nAs I mentioned in [this blog framework ain't perfect](https://aaronspindler.com/b/0004_this_blog_aint_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.\n\nSo 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.\n\nAnd if you don't believe me, just look at the Lighthouse scores. The numbers speak for themselves.", "word_count": 1755, "content_hash": "6fdc07ba39b97d7bbe02b510199c4f998b7a922ba4fa7721509e2bb68494212c"}