Браузер кэширует ресурсы по полному УРЛ, включая параметры запроса.
Поэтому выход очень простой, и он повсеместно используется.
Нужно дописать в queryString любой уникальный параметр.
В лоб можно писать метку времени
<script src="my_script.js?<?= time() ?>">
Однако, с таким подходом файл будет постоянно загружаться заново при каждом запросе.
Лучше ввести версионирование. Простейший вариант – вручную обновлять версию при изменении
<script src="my_script.js?v=1">
<script src="my_script.js?v=2">
Но это, конечно, неудобный вариант. Нужно автоматизировать. Например, при сборке каким-нибудь бандлером можно настроить имя выходного файла на основе хеша его содержимого.
Будет выглядеть как-то так
<script src="my_script.ab21df.js">
Такой вариант хорошо подходит для SPA, так как сборщик тогда обычно генерит и индексный файл и может в нем автоматически подключать актуальные имена файлов.
В остальных случаях отлично подойдет вариант с подстановкой метки времени изменения файла
<script src="my_script.js?v=<?= filemtime('/path/to/my_script.php') ?>">