After 100+ projects on Upwork and hundreds more with private clients, I've settled on a stack. It's not the most exciting or the most trendy. It's the one that lets me ship reliable software solo, on deadline, without things falling apart at 2 AM.

Here's what I use, why I chose it, and what I've stopped using.

The Stack

Layer Choice Why
Frontend Next.js + React SSR when needed, static when possible, one framework for everything
Styling Tailwind CSS Speed. No context-switching between files
Backend (complex) Django + DRF Batteries included. ORM, auth, admin, migrations - all handled
Backend (simple) Next.js API routes When Django is overkill. Small APIs, webhooks, form handlers
Database PostgreSQL Never regretted it. Not once
Task Queue Celery + Redis Background jobs, scheduled tasks, email queues
Deployment VPS (Ubuntu) Full control, predictable cost, no vendor lock-in

That's it. No Kubernetes. No microservices. No GraphQL. Just tools that I can debug at 2 AM when a client's site is down.

Why Next.js

I tried Vue, Svelte, and plain React with Vite. They're all good. But Next.js wins for freelancing because of one thing: it handles the boring stuff.

  • Routing is file-based - no router config
  • SSR and static generation are built in - good for SEO-sensitive client sites
  • API routes mean simple backends don't need a separate server
  • Image optimization, fonts, and metadata are handled
  • Vercel deployment is free for small projects (though I usually self-host)

For a typical client project - a marketing site with a dashboard - Next.js handles both the public-facing pages (static/SSR) and the authenticated dashboard (client-side) in one codebase.

project/
├── app/
│   ├── page.tsx              # Marketing homepage (static)
│   ├── pricing/page.tsx      # Pricing page (static)
│   ├── dashboard/
│   │   ├── page.tsx          # Dashboard (authenticated, client-side)
│   │   └── settings/page.tsx
│   └── api/
│       ├── webhook/route.ts  # Stripe webhook handler
│       └── contact/route.ts  # Contact form handler

One project. One deployment. One thing to maintain.

Why Django (and When Not to Use It)

Django is my go-to when the project needs:

  • Complex data models with relationships
  • User authentication and permissions
  • An admin panel (clients love the admin)
  • Background task processing
  • Database migrations that don't break things

The Django admin alone saves me 20-40 hours per project. Instead of building a custom admin dashboard for the client to manage content, I configure the Django admin:

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ['name', 'price', 'is_active', 'created_at']
    list_filter = ['is_active', 'category']
    search_fields = ['name', 'description']
    list_editable = ['is_active', 'price']

That's a fully functional content management interface. For free. Clients who need to update products, manage orders, or moderate content get a polished admin from day one.

When I skip Django: Simple APIs, static sites with a contact form, projects where the frontend is 90% of the work. For those, Next.js API routes are enough.

Why PostgreSQL (Always)

I've used MySQL, MongoDB, and SQLite in client projects. I always come back to PostgreSQL.

  • JSON fields - Need a flexible schema for one table? JSONField in Django, jsonb in Postgres. No need for MongoDB
  • Full-text search - Built in. SearchVector in Django. No need for Elasticsearch on small-to-medium projects
  • Reliability - I have never lost data with PostgreSQL. I can't say the same for MongoDB
  • Tooling - pg_dump, pg_restore, point-in-time recovery. Backups are simple and reliable

The "MongoDB for flexibility" argument falls apart when you realize PostgreSQL's jsonb gives you the same flexibility with the safety of a relational database underneath.

Why VPS Over PaaS

I deploy most projects on a $10-20/month VPS (Hetzner, DigitalOcean, or the client's existing server). Why not Vercel, Railway, or Fly.io?

Cost predictability. A Next.js app on Vercel is free until it isn't. One viral blog post or a bot hitting your site, and you're staring at a surprise bill. A VPS costs the same whether you get 10 or 10,000 requests.

Full control. I can SSH in, check logs, restart services, and debug issues directly. No waiting for platform support to tell me why my deployment failed.

Client handoff. When the project is done, I hand over a VPS with everything running. The client's next developer doesn't need to understand Vercel's build pipeline or Railway's Procfile - they SSH in and see a standard Linux server.

My typical server setup:

Ubuntu 22.04 LTS
├── Nginx (reverse proxy + static files)
├── Gunicorn (Django application server)
├── Node.js (Next.js production server)
├── PostgreSQL
├── Redis (Celery broker + caching)
├── Certbot (SSL via Let's Encrypt)
└── Supervisor (process management)

Total cost: $10-20/month. Handles far more traffic than you'd think.

Tools I Stopped Using

MongoDB

Switched to PostgreSQL + JSONField. Same flexibility, better guarantees. The only time I'd use MongoDB now is if the client's existing infrastructure requires it.

GraphQL

It's powerful but overkill for 95% of freelance projects. REST with Django REST Framework is faster to build, easier to debug, and simpler for the next developer to understand. I only use GraphQL if the client specifically requests it.

Docker in Production

For solo projects on a single VPS, Docker adds complexity without proportional benefit. I use it for development environments (consistent setup across machines) but deploy directly with Gunicorn + Supervisor.

Microservices

A monolith serves a team of one. All the code is in one repo, one deployment, one set of logs. The "microservices for scalability" argument doesn't apply when the entire project fits in one developer's head.

The Project Template

Every new project starts from the same structure:

client-project/
├── frontend/              # Next.js app
│   ├── app/
│   ├── components/
│   ├── lib/
│   └── package.json
├── backend/               # Django project
│   ├── config/
│   ├── apps/
│   ├── requirements.txt
│   └── manage.py
├── deploy/                # Deployment scripts
│   ├── nginx.conf
│   ├── gunicorn.conf.py
│   └── supervisor.conf
└── README.md

Having a consistent structure means I don't waste time on setup. Clone the template, rename things, start building features.

Estimating Projects

After 100+ projects, my rough estimates:

Project Type Timeline
Marketing site (5-10 pages) 1-2 weeks
Marketing site + CMS 2-3 weeks
Web app (auth, dashboard, CRUD) 3-5 weeks
Web app + integrations (Stripe, email, etc.) 4-7 weeks
Scraping tool / automation 1-3 weeks
Desktop app (PyQt5) 2-4 weeks

These include client communication, revisions, and deployment. The actual coding is usually 40-60% of the timeline.

Key Takeaways

  • Pick boring technology. It's boring because it works
  • PostgreSQL handles 95% of use cases - relational, JSON, full-text search
  • Django admin saves 20-40 hours per project
  • VPS gives you predictable costs and full control
  • A monolith is the right architecture for a team of one
  • Consistency across projects matters more than using the latest framework
  • The best stack is the one you can debug at 2 AM

Trendy stacks are for conference talks. Shipping stacks are for paying clients.


Your stack should match your constraints. Solo freelancer? Optimize for speed and maintainability. Team of 20? Different calculus entirely.