できない.dev

Django で静的ファイル(CSS・JS)が 404 になり読み込めない(DEBUG=False・collectstatic)

runserver が静的ファイルを配信するのは DEBUG = True のときだけだ。
本番では STATIC_ROOT に collectstatic で集めて Web サーバから配信し、開発中の 404 は置き場所と STATICFILES_DIRS を確認する。

公開:

要約

django.contrib.staticfiles を入れたプロジェクトでは、runserver が /static/ 配下のファイルを自動で配信する。
ただしこれは DEBUG = True のときだけの動作で、公式ドキュメントは「非常に非効率で、おそらく安全でもない」ため本番には向かないと明記している。

そのため、DEBUG = False に切り替えた途端に、CSS や JavaScript などの静的ファイルだけが 404 になる。
本番では次の 2 段階で配信する。

python manage.py collectstatic
# 集まった STATIC_ROOT を Web サーバから配信する

よくある原因

  1. DEBUG = False にした: runserver の静的ファイル配信は止まる。
    ページ本体は表示されるのに、スタイルが外れた状態になる。
  2. collectstatic を実行していない: 本番の Web サーバは各アプリの static/ を見に行かない。collectstatic で 1 か所に集め、そのディレクトリを配信する必要がある。
  3. STATIC_ROOT が未設定: 既定値は None で、このまま collectstatic を実行すると ImproperlyConfigured で止まる(全文は解決策 2)。
  4. STATICFILES_DIRS が無い: Django が標準で探すのは各アプリの static/ サブディレクトリだ。
    プロジェクト直下の static/site.css は、DEBUG = True でも登録しない限り 404 になる。

解決策

1. ローカルでは DEBUG を戻すか --insecure を使う

DEBUG = False の挙動を手元で確かめたいときは、--insecure を付けると静的ファイルも配信される。

python manage.py runserver --insecure

公式ドキュメントは、このオプションはローカル開発専用で本番では決して使わないよう警告している。DEBUG = False にするなら、ALLOWED_HOSTS の設定も同時に必要になる。

2. collectstatic して Web サーバから配信する

STATIC_ROOT が未設定のまま collectstatic を実行すると、次のエラーになる。

django.core.exceptions.ImproperlyConfigured: You're using the staticfiles app without having set the STATIC_ROOT setting to a filesystem path.

STATIC_ROOT を決めて、デプロイのたびに collectstatic を実行する。

# settings.py
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
python manage.py collectstatic --noinput

成功すると … static files copied to '<STATIC_ROOT のパス>'. の形で、コピーした件数が表示される。
集めたディレクトリを、nginx などで STATIC_URL のパスに割り当てる。

location /static/ {
    alias /app/staticfiles/;
}

3. STATIC_ROOT は置き場所と分ける

STATIC_ROOT は collectstatic の出力先で、ファイルを手で置く場所ではない。STATICFILES_DIRS と同じディレクトリを指定すると、システムチェックで止まる。

?: (staticfiles.E002) The STATICFILES_DIRS setting should not contain the STATIC_ROOT setting.

4. アプリに属さないファイルは STATICFILES_DIRS に登録する

STATICFILES_DIRS = [BASE_DIR / "static"]

アプリのファイルは blog/static/blog/app.css のように、アプリ名のサブディレクトリを挟んで置く。
公式ドキュメントは、別のアプリに同じ名前のファイルがあると Django が区別できず、最初に見つかったものを使うため名前空間を切るよう説明している。
テンプレートでは {% load static %} の後に {% static "blog/app.css" %} で URL を組み立てる。

この記事は役立ちましたか?