Django is a Python web framework that can serve very fast pages with the right configuration. Here's how to optimize a Django site for top PageSpeed scores.
Django is a batteries-included Python web framework used by some of the highest-traffic sites on the web. With the right setup, Django sites can score 90+ on PageSpeed. Without it, they often score in the 40s-60s due to slow TTFB and unoptimized assets.
Here's how to optimize Django for performance.
Everything starts with how quickly Django responds to requests. A slow TTFB means slow FCP, LCP, and everything else.
Never run python manage.py runserver in production. Use Gunicorn or uWSGI:
gunicorn myapp.wsgi:application --workers 4 --bind 0.0.0.0:8000
Workers should be 2 * CPU cores + 1. For async views, consider Uvicorn with Gunicorn:
gunicorn myapp.asgi:application -k uvicorn.workers.UvicornWorker --workers 4
Unoptimized database queries are the most common cause of slow TTFB. Use Django's query optimization tools:
# Bad: N+1 query problem
for article in Article.objects.all():
print(article.author.name) # One query per article
# Good: Single query with JOIN
articles = Article.objects.select_related("author").all()
For many-to-many or reverse FK relationships:
articles = Article.objects.prefetch_related("tags").all()
Enable query logging in development to catch N+1 problems before they hit production:
# settings.py (development only)
LOGGING = {
"version": 1,
"handlers": {"console": {"class": "logging.StreamHandler"}},
"loggers": {
"django.db.backends": {
"handlers": ["console"],
"level": "DEBUG",
}
},
}
Django's cache framework supports multiple backends. Use Redis for production:
pip install django-redis
# settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
}
}
Cache expensive views with the cache_page decorator:
from django.views.decorators.cache import cache_page
@cache_page(60 * 60) # Cache for 1 hour
def blog_list(request):
articles = Article.objects.select_related("author").published()
return render(request, "blog/list.html", {"articles": articles})
Or cache at the template fragment level for pages with some dynamic content:
{% load cache %}
{% cache 3600 article_list %}
<!-- expensive template fragment -->
{% endcache %}
WhiteNoise serves static files efficiently from Django itself with correct cache headers and compression:
pip install whitenoise
# settings.py
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
# ...
]
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
WhiteNoise adds Brotli and gzip compression automatically and sets long-term cache headers for versioned files.
For higher traffic, serve static files from a CDN:
# settings.py
STATIC_URL = "https://your-cdn.example.com/static/"
MEDIA_URL = "https://your-cdn.example.com/media/"
Use django-storages with S3 or a similar service:
pip install django-storages boto3
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
STATICFILES_STORAGE = "storages.backends.s3boto3.S3StaticStorage"
Serve assets from S3 + CloudFront for global CDN distribution.
python manage.py collectstatic --noinput
This copies all static files to STATIC_ROOT and (with WhiteNoise or custom storage) adds content hashes to filenames for long-term caching.
Use a bundler like Vite or webpack for your frontend assets:
npm install vite
Or use Django's built-in static files with a minification library:
pip install django-compressor
INSTALLED_APPS = ["compressor", ...]
COMPRESS_ENABLED = True
COMPRESS_OFFLINE = True # Pre-compress for production
In templates:
{% load compress %}
{% compress css %}
<link rel="stylesheet" href="{% static 'css/main.css' %}">
{% endcompress %}
In your base template, ensure scripts don't block rendering:
<!-- Blocks rendering - avoid -->
<script src="{% static 'js/analytics.js' %}"></script>
<!-- Deferred - good -->
<script defer src="{% static 'js/analytics.js' %}"></script>
Use Pillow (already a Django dependency) or ImageKit for automatic image optimization:
pip install django-imagekit
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit
class Article(models.Model):
image = models.ImageField(upload_to="articles/")
image_thumbnail = ImageSpecField(
source="image",
processors=[ResizeToFit(800, 600)],
format="WEBP",
options={"quality": 85},
)
Prevent layout shift in templates:
<img
src="{{ article.image.url }}"
width="{{ article.image.width }}"
height="{{ article.image.height }}"
alt="{{ article.title }}"
loading="lazy"
>
For your hero/LCP image, use loading="eager" (or omit the attribute).
Django opens a new database connection per request by default. Use a connection pooler:
pip install django-db-connection-pool
Or use PgBouncer in front of PostgreSQL for high-traffic deployments.
Django performance varies significantly by hosting:
For database: use managed PostgreSQL (Supabase, RDS, DigitalOcean Managed Database) rather than running PostgreSQL on the same server as your app.
runserver)select_related, prefetch_related)collectstatic run as part of deploymentDjango can be very fast -- Instagram, Pinterest, and Disqus all use or used Django at massive scale. But it requires configuration. The defaults are safe, not fast.
Test your Django site to see your current PageSpeed score and find out where the bottlenecks are.
How fast is your site?
Get your PageSpeed score in seconds — free, no sign-up needed.
Test Your Site →