A Django site renders fine locally but raises 'ValueError: Missing staticfiles manifest entry' in production; why, and how do you fix it?
answer
- works locally, fails after deploy
- DEBUG changes which branch runs
- manifest_strict is on by default
- manifest loaded once per process
- test runner forces DEBUG off
basics
~10 sManifestStaticFilesStorage consults staticfiles.json only when DEBUG is False, and manifest_strict makes a missing name raise ValueError. The file never reached the manifest the process loaded; fix the collectstatic step, not the flag.
solid answer
~50 sWith `DEBUG = True` the storage's `url()` returns the unhashed name without looking at the manifest, so a typo or an uncollected file never fails in development. In production `url()` calls `stored_name()`, which looks the name up in the map loaded from `staticfiles.json`; because `manifest_strict` defaults to `True`, a miss raises `ValueError` and the template render becomes a 500. The usual causes: `collectstatic` did not run for this release, the path is misspelled or starts with a slash, no finder sees the file so it was never collected, the workers loaded the manifest before `collectstatic` rewrote it, or the manifest file is missing and the map is empty. The fix is to run `collectstatic` in every build before the app starts, let the build fail on errors, and reproduce with `DEBUG = False`. Setting `manifest_strict = False` in a subclass does not make the problem go away.
code
python · 9 lines# settings/test.py
from .base import * # noqa: F403
STORAGES = {
**STORAGES, # noqa: F405
'staticfiles': {
'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage',
},
}go deeper
Recall that hashed static names only appear with DEBUG off and that collectstatic must run on every deploy for them to exist.
Explain the two branches of the storage's url(), the exact-match lookup in the manifest, and why manifest_strict turns a miss into a ValueError and a 500.
Diagnose the release pipeline: collect before processes start, fail the build on errors, restart workers on a new manifest, and give tests their own storage backend.
Decide how the team guarantees template and manifest stay in step, for example a post-deploy render check, instead of relaxing strict mode to keep pages up.
## The symptom A page renders in development, passes review, and after a deploy returns **500 Internal Server Error**. The log shows `ValueError: Missing staticfiles manifest entry for 'css/new-widget.css'`, raised while a template evaluated `{% static 'css/new-widget.css' %}`. The project uses `ManifestStaticFilesStorage` as its `STORAGES['staticfiles']` backend. ## Why development never shows it The storage's `url()` method has two branches. When `settings.DEBUG` is `True` it returns the name unchanged, joined to `STATIC_URL`, and **never reads the manifest**. When `DEBUG` is `False` it calls `stored_name()`, which looks the cleaned name up in `hashed_files`, the dictionary loaded from `staticfiles.json` when the storage object was created. So any mismatch between templates and the manifest is invisible with `DEBUG = True` and fatal without it. ## The lookup that fails `manifest_strict` is a class attribute that defaults to `True`. On a miss, `stored_name()` raises `ValueError("Missing staticfiles manifest entry for '...'")`. The exception escapes template rendering, so the whole response fails, not just one asset. Lookups are **exact string matches** on the relative name, so `css/app.css` and `/css/app.css` are different keys. ## The usual causes 1. **`collectstatic` did not run for this release**, or ran against an older checkout, so the new file is not in `paths`. 2. **The path in the template is wrong**: a typo, a leading slash, or `STATIC_URL` repeated inside the tag argument. 3. **The file was never collected** because it sits in a directory no finder searches, so it is not in `STATIC_ROOT` either. 4. **The processes loaded an older manifest.** The manifest is read once per process; if `collectstatic` rewrote it after the workers started, they keep the old map until restarted. 5. **The manifest is absent.** If `staticfiles.json` cannot be found, the map is simply empty and every lookup misses (a corrupt file raises a different `ValueError`, "Couldn't load manifest"). 6. **Tests.** Django's test runner forces `DEBUG = False`, and `collectstatic` is not part of test setup, so the same error appears in any test that renders `{% static %}`. ## Fixing it properly - Run `collectstatic --noinput` as part of **every build**, before the new application processes start, and let a `CommandError` fail the build. - Reproduce locally by running `collectstatic` and then serving with `DEBUG = False`; the error appears at once. - In test settings, point `STORAGES['staticfiles']` at `django.contrib.staticfiles.storage.StaticFilesStorage`, which the Django docs recommend for tests. - Restart or reload workers whenever the manifest changes outside a fresh build. - A post-deploy smoke check that renders a page with DEBUG off catches the error before users do. ## Why manifest_strict = False is not the fix Subclassing the storage and setting `manifest_strict = False` changes the miss path: instead of raising, it tries to hash the file **from disk at request time**. The documentation describes this as leaving nonexistent paths unchanged, but in the Django 6.1 code (and its own test suite) a file that is missing on disk still raises `ValueError`, this time "The file '...' could not be found". | Situation | `manifest_strict = True` | `manifest_strict = False` | |---|---|---| | Name in the manifest | hashed URL | hashed URL | | Missing from manifest, present in `STATIC_ROOT` | `ValueError` | file hashed on each call; the hashed copy may not exist, so the URL can 404 | | Missing from manifest and from disk | `ValueError` | `ValueError` ("could not be found") | The flag trades a loud, early failure for per-request disk reads and URLs that may point nowhere. Keep strict mode and fix the build.
- Why do Django tests hit the same ValueError when nothing has been deployed?Django's test runner sets `DEBUG = False` whatever your settings say, so `{% static %}` goes through the manifest lookup. `collectstatic` is not part of test setup, so `staticfiles.json` is usually missing and every lookup misses. Use `StaticFilesStorage` in test settings, as the docs recommend, or run `collectstatic` before the tests.
- collectstatic finished successfully after the new release started, yet the errors continue; why?The manifest is loaded when the `staticfiles` storage object is first created in each process and is then held in memory. Processes that started before `collectstatic` rewrote the file still have the old map. Restart them, or better, collect before the new processes start.
- What does subclassing with manifest_strict = False actually do on a miss in Django 6.1?`stored_name()` falls back to `hashed_name()`, which opens the file in `STATIC_ROOT` and hashes it on that call. If the file exists, you get a hashed URL whose copy may never have been saved; if it does not exist, `ValueError` is raised anyway. It hides build problems rather than fixing them.
saying these in an interview costs you the question
- The error means the static file is corrupt and must be re-uploaded.
- Turning DEBUG on in production is an acceptable fix.
- Setting manifest_strict = False makes every missing file render harmlessly.
- Django re-reads staticfiles.json on each request, so rerunning collectstatic is enough.
- It cannot happen in tests because tests use the development settings.