skip to content

A Django site renders fine locally but raises 'ValueError: Missing staticfiles manifest entry' in production; why, and how do you fix it?

level: seniorimportance: should knowfreq 45%

answer

  1. works locally, fails after deploy
  2. DEBUG changes which branch runs
  3. manifest_strict is on by default
  4. manifest loaded once per process
  5. test runner forces DEBUG off

basics

~10 s

ManifestStaticFilesStorage 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 s

With `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
python
# settings/test.py
from .base import *  # noqa: F403

STORAGES = {
    **STORAGES,  # noqa: F405
    'staticfiles': {
        'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage',
    },
}

go deeper

for a junior

Recall that hashed static names only appear with DEBUG off and that collectstatic must run on every deploy for them to exist.

for a middle

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.

for a senior

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.

for a principal

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.