【Django】CSS や JavaScript の変更が反映されないときの対処法

2022年7月6日 2023年11月8日
カテゴリ: プログラミング
プログラミング Python JavaScript Django CSS HTML

フロントエンドをやっていると CSS や JavaScript のファイルを変更したのになぜか反映されなくて困ったという経験は誰にでもあるかと思います。
これはブラウザが CSS や JavaScript のファイルをキャッシュして使用していることが原因なのですが、初心者だとそこまで気が回らないものですよね。

対処法としてはブラウザのキャッシュをクリアしたり、スーパーリロード (Force Reload といったりもします) する必要があります。
ですが、その場合ユーザ側で操作する必要があり、いちいち対応してもらうのは非現実的です。

そこで、CSS や JavaScript を変更した際にキャッシュを使用せずサイト側のファイルを参照させるための方法としてクエリパラメータの仕組みを利用します。
クエリパラメータは HTML で CSS や JavaScript のファイルを指定する際、ファイル名の後ろに「?=文字列」という風に書きます。

<link rel="stylesheet" href="style.css?=abc">

そもそもキャッシュとは

ブラウザは Web サイトに接続した際、CSS や JavaScript、画像などのファイルをキャッシュという領域に保存します。
そして、2 回目以降の接続の際これらのファイルはキャッシュから読み込まれます。

これは、余計な通信が発生しないようにしてよく利用するサイトの表示を高速化させるための仕組みです。

キャッシュが所定の容量を超えたり (期限が設定されている場合) 使用期限を超えたり、ユーザの操作でクリア等しない限りキャッシュ上に存在するものが使用されます。

たとえば「style.css」という CSS を使用しているサイトに接続した際、いくらサイト側のファイルの中身が変更されていようとキャッシュに同じ名前 (URL) のファイルが存在すればキャッシュを優先的に使用します。

CSS や JavaScript の変更が反映されない理由はこの辺りにあります。

クエリパラメータでキャッシュクリアが不要になる仕組み

クエリパラメータとは、先述のとおり URL の後ろに 「?=文字列」という形式で記述します。
これは Django や PHP などの動的ページではこの文字列を読み取ってそれに応じた画面を表示したりする処理に利用されます。

そして、CSS や JavaScript といった静的なファイルにクエリパラメータを付けると通常のファイルと認識されるだけで、特に何も起こりません

例えば下記のようにファイルを指定した場合、キャッシュにファイルが存在しなければサイト側の「style.css」を参照しに行きます。
キャッシュに存在すればキャッシュにあるものを使用します。

<link rel="stylesheet" href="style.css?=abc">

ただし、クエリパラメータが違うものは同じファイル名でも別のファイルとしてキャッシュします

この仕組みがクエリパラメータを使用する最大の理由です。

つまりどういうことかというと、CSS や JavaScript を変更した際は HTML 側のクエリパラメータも一緒に変更するのです。
そうすることで、サイトに接続した際キャッシュに存在しないファイルが指定されているので、新たにファイルを読み込みに行くという処理が実行されます。

ここで使用するクエリパラメータは何でも大丈夫です。
以前に使用していないものであれば、キャッシュには存在しないはずなので CSS や JavaScript の変更は確実に反映されます。

クエリパラメータは何でも大丈夫ですが、ファイルの更新日時や (バージョン管理していれば) バージョン番号などを使用するのが一般的なようです。

Django でクエリパラメータを自動的に追加・変更する方法を考える

CSS や JavaScript の変更を行う際、都度 HTML を手動で変更しても問題ありませんが、Django の処理でクエリパラメータを自動的に更新する方法を考えてみましょう。

方針としては views でファイルの更新日を取得して template に渡すという感じにします。

ファイルの更新日を取得する

まずは views の中にファイルの更新日を取得する関数を作ります。

対象は本番環境で使用する static ファイルです。
プロジェクト構成によって場所は変わってきますが collectstatic コマンドで集約される場所にあります。
static ファイルの場所を取得するには django.templatetags.static を使用します。

また、ファイルの更新日時を取得するには os.path.getmtime を使用します。
os.path.getmtime の戻り値は起点となる日時 (エポック) からの経過秒数を表す浮動小数点数なので、
これをタイムスタンプに直すために datetime.fromtimestamp を使用し、datetime.strftime で文字列に変換します。

関数名は get_modified_date としました。

views.py
from django.templatetags.static import static
from django.conf import settings
import os
from datetime import datetime

def get_modified_date(filepath):
    try:
        static_file_path = static(filepath)
        base_dir = settings.BASE_DIR
        static_file_url = os.path.join(base_dir, *static_file_path.split("/"))

        modified_serial_time = os.path.getmtime(static_file_url)
        modified_date = datetime.fromtimestamp(modified_serial_time).strftime("%Y%m%d")

        return modified_date

    except:
        return ""

使い方は次のようになります。

views.py
def sample(request):

    ... (中略)

    context = {
        "style_css_date": get_modified_date("css/style.css"),
    }

    ... (中略)

    return render(request, 'app/main.html', context)

template にクエリパラメータを追加する

views から template にファイルの更新日を渡すことができたので、これを用いてクエリパラメータを追加します。

template
{% load static %}

... (中略)

<link rel="stylesheet" href="{% static 'css/style.css' %}{% if style_css_date %}?={{ style_css_date }}{% endif %}">

これで CSS や JavaScript を変更したら自動的にクエリパラメータも変わるようになります。

この場合、更新した年月日までしか使用していないので一日に何度も CSS や JavaScript を変更する場合には対応できません。
ファイルを更新した時分秒まで使用したい場合は views で時間を文字列に変換する際 datetime.strftime("%Y%m%d%H%M%S") とすれば OK です。

関連の記事

Sphinxで作ったドキュメントの外部リンクを新しいタブで開く方法を考える

文章を全部読むまで押せないチェックボックスを作ってみよう

ラジオボタンを未選択の状態にする方法を考えよう

CSSで色見本のアイコンを作ろう