Django Static Files Production Checklist
A production checklist for Django static files: STATIC_ROOT and collectstatic in the build, manifest storage for cache busting, serving with WhiteNoise, Nginx, or a CDN, cache headers, compression, frontend bundles, and admin assets.
Problem statement
Static files are the most common first-deploy failure in Django: unstyled admin, 404s for CSS, stale JavaScript after a deploy, or slow pages because assets are uncompressed and uncached. They are also easy to get permanently right with a few decisions: where files are collected, how filenames change when content changes, who serves them, and what cache headers they carry. This checklist walks through each.
- Set
STATIC_URLand an absoluteSTATIC_ROOTseparate from sourceSTATICFILES_DIRS. - Run frontend builds, then
collectstatic --noinput, at build or deploy time. - Use
ManifestStaticFilesStorageor WhiteNoise'sCompressedManifestStaticFilesStoragefor hashed filenames. - Serve with WhiteNoise, Nginx, or a CDN; never with
DEBUG = True. - Send
Cache-Control: public, max-age=31536000, immutablefor hashed files. - Compress text assets (gzip and Brotli) and confirm admin assets load.
Checklist
Settings
STATIC_URL = "/static/"(or a CDN URL).STATIC_ROOT = BASE_DIR / "staticfiles", not insideSTATICFILES_DIRS, ignored by Git.STORAGES["staticfiles"]uses a manifest storage backend.- Templates use
{% static %}, never hard-coded/static/...paths.
Build and deploy
- Frontend assets (Vite, Tailwind, webpack) built before
collectstatic, with output inside aSTATICFILES_DIRSpath. collectstatic --noinputruns in the Docker build or deploy script, not at container start.- Build fails if a referenced file is missing (manifest storage raises during
collectstaticor rendering).
Serving and caching
- Hashed files served with a one-year, immutable cache header (WhiteNoise does this by default).
- Unhashed files (for example
robots.txt, favicons at fixed paths) have short cache lifetimes. - Compression: WhiteNoise pre-compresses at collectstatic; Nginx uses
gzip_staticor on-the-fly gzip; CDNs compress automatically. - CORS headers present for fonts if served from another domain.
Verification
curl -I https://example.com/static/admin/css/base.cssreturns 200 with cache headers.- A changed CSS file gets a new hashed name after deploy; pages reference the new name.
- Browser dev tools show static files compressed and served from cache on reload.
DEBUG = Falselocally (withrunserver --insecureor WhiteNoise) to catch manifest errors before deploy.
Explanation
Django separates static files' source locations from their production location: collectstatic gathers files from apps and STATICFILES_DIRS into STATIC_ROOT. Manifest storage adds a content hash to each filename and rewrites references in CSS, so a changed file gets a new URL and can be cached indefinitely. Serving is then a matter of mapping STATIC_URL to STATIC_ROOT efficiently, through middleware, a web server, or a CDN. Most failures come from skipping one of these steps or running with DEBUG assumptions in production.
Edge cases / notes
- Missing manifest entry errors mean a template references a file that
collectstaticdid not produce; fix the path or build step. - Source maps referenced in CSS or JS must exist or be stripped, or manifest storage fails.
- Multiple servers each need the same collected files (bake them into the image or use a CDN/S3).
- Media files are not static files; they need separate storage and serving.
Next steps
- Serve with WhiteNoise: Serve Static Files With WhiteNoise in Django.
- Fix broken assets: Fix Django Static Files Not Loading.
FAQ
Why are Django static files not loading in production?
Usually collectstatic did not run, STATIC_ROOT is not served, or DEBUG is off with no WhiteNoise or Nginx configured.
Should collectstatic run at container start?
No. Run it at build time so containers start fast and identically.
What is manifest storage?
A storage backend that adds content hashes to filenames, enabling long-term caching with automatic cache busting.
Do I need a CDN for static files?
Not necessarily. WhiteNoise or Nginx with proper cache headers is fine; a CDN helps for global or heavy traffic.