← Все статьи

SiteGuard11 мин чтения

CSS и JavaScript не загружаются из-за MIME-типа: что проверить на сервере

Страница открывается без оформления, меню не реагирует, а браузер сообщает об ошибке MIME type при загрузке JavaScript или CSS. Причина не обязательно в самом файле стилей или скрипте. По его адресу сервер мог вернуть страницу входа, HTML приложения или сообщение об ошибке. Браузер отказывается использовать такой ответ как запрошенный ресурс.

Исправление начинается с фактического ответа: какой URL запросили, какие статусы и перенаправления получили, что находится в теле и какой указан Content-Type. Только после этого выбирают изменение маршрутизации, публикации файлов или настройки типов. Удаление защитного заголовка и назначение JavaScript-типа любой странице проблему не устраняют.

Что означает ошибка strict MIME type checking

MIME-тип сообщает назначение содержимого. Для таблицы стилей ожидается text/css, для JavaScript рекомендуется text/javascript. Эти значения приведены в руководстве MDN по настройке MIME-типов сервера. Расширение .js в адресе само по себе не доказывает, что сервер действительно отправил скрипт.

Сообщение Refused to execute script или strict MIME type checking описывает отказ браузера принять полученное содержимое в нужном качестве. Оно не устанавливает, какой компонент сформировал неверный ответ. Источником может оказаться приложение, веб-сервер, прокси, CDN или промежуточная проверка доступа.

Полезно сразу разделить две ситуации. В первой тело содержит правильный JavaScript или CSS, но тип указан неверно. Во второй по адресу ресурса пришёл другой документ. Назначение text/javascript странице входа во втором случае лишь неверно подпишет HTML и не превратит его в работающий код.

Проверяйте Content-Type ответа и сохраняйте nosniff

MDN определяет Content-Type как заголовок, сообщающий тип представления ресурса. При диагностике CSS и JavaScript нужен заголовок полученного ответа. Изменение одноимённого заголовка запроса в браузерном коде обычно не исправляет то, как сервер публикует статический файл.

Заголовок X-Content-Type-Options: nosniff требует уважать объявленный тип; для запросов скриптов и стилей браузер блокирует неподходящие типы. Его не следует отключать ради исчезновения сообщения. Правильное исправление обеспечивает ожидаемое содержимое с подходящим Content-Type и сохраняет защиту.

Не путайте MIME-тип с кодировкой текста или сжатием. Параметр charset=utf-8 уточняет кодировку, а Content-Encoding описывает применённое кодирование содержимого, например сжатие. CSS, переданный со сжатием, всё равно остаётся CSS. Диагноз должен опираться на сочетание заголовков и декодированного тела, а не на одно незнакомое слово в панели сети.

Найдите точный запрос, который отверг браузер

Откройте инструменты разработчика и вкладку Network, затем воспроизведите проблему. Найдите адрес из сообщения консоли и сохраните запись запроса. Если скрипт загружает другие модули или части сборки, неисправным может быть дочерний файл, хотя основной пакет пришёл успешно. Проверять нужно конкретный отказавший URL.

Зафиксируйте полный адрес с путём и параметрами, время, страницу-источник и состояние авторизации. Посмотрите, какой элемент или импорт инициировал запрос. Относительный путь способен вести в другую папку на вложенной странице: адрес ресурса из HTML нужно сравнивать с тем адресом, который браузер фактически запросил.

Например, без изменения базового адреса ссылка assets/app.js со страницы /catalog/item/ ведёт к /catalog/item/assets/app.js, а ссылка /assets/app.js — к пути от корня сайта. Это учебное сравнение помогает проверить гипотезу: главная работает, а вложенная страница получает HTML вместо скрипта. Если на сайте задан отдельный базовый адрес, ориентируйтесь на вычисленный URL из Network, а не на механическое добавление слеша.

Открытие файла отдельной вкладкой полезно как дополнительная проверка, но не полностью воспроизводит загрузку скрипта. Контекст запроса и правила обработки могут отличаться. Основным доказательством остаётся запись исходного запроса ресурса. Перед передачей выгрузки подрядчику удалите cookies, токены и другие данные доступа; для поиска причины обычно достаточно безопасного описания ответа.

Проследите статусы и всю цепочку перенаправлений

Проверяйте не только конечный статус. Запрос /assets/app.js может получить перенаправление на /login, после которого браузер увидит успешный HTML-ответ. Если посмотреть только последние «200 OK», причина останется скрытой. В журнале диагностики сохраните исходный адрес, переходы и конечный URL.

НаблюдениеЧто оно означает для проверки
200 и JavaScript в телеПроверить объявленный тип и источник его назначения
200 и HTML приложенияПроверить запасной маршрут, публикацию файла и выбор обработчика
Перенаправление на вход, затем HTMLРазобраться с правилами доступа к нужному ресурсу
Ошибка сервера с HTML-страницейУстановить причину этого ответа, сохранив корректный статус ошибки
Ответ получен из кешаУстановить, какой сохранённый объект и заголовки использованы

Если используете HTTP-клиент, выполняйте проверку, которая позволяет увидеть тело ответа. Один запрос HEAD показывает лишь заголовки и не доказывает, что вместо скрипта не пришёл другой документ. При автоматическом следовании перенаправлениям сохраняйте их цепочку, иначе итоговый ответ скроет важное промежуточное решение.

Сначала прочитайте тело, затем объясняйте заголовок

Во вкладке Response посмотрите содержимое ответа. Начало <!doctype html>, форма входа, фирменная страница ошибки или проверка браузера сразу меняют направление расследования. Это признаки другого документа. Для небольшого ресурса можно сравнить его содержимое с ожидаемым файлом сборки; для большого — использовать безопасную контрольную сумму и известную версию.

Если пришёл HTML с Content-Type: text/html, заголовок может быть совершенно корректным для этого тела. Ошибка находится раньше: система выбрала HTML вместо запрошенного CSS или JavaScript. Подрядчик должен установить, кто принял это решение, и восстановить выдачу нужного ресурса.

Если тело действительно содержит CSS, а тип равен text/plain, проверяют таблицу типов и применённые правила ответа. Если обнаружен JavaScript с application/octet-stream, выясняют, почему ресурс получил общий бинарный тип. В обоих случаях исправление должно действовать только на подходящее содержимое, а не на все ответы сайта.

Установите также, где хранится ресурс. При выдаче из объектного хранилища или отдельного домена статики источник типа может находиться в метаданных опубликованного объекта. Изменение настроек основного веб-сервера тогда не затронет проблемный ответ. Сравните разрешённым способом исходную выдачу и публичный адрес, чтобы понять, на каком участке появился неверный заголовок. Проверку выполняют с учётом реальных правил доступа и имени хоста.

Пустое тело или неожиданный текст также нельзя автоматически считать ошибкой таблицы MIME. Сопоставьте их со статусом, размером и источником ответа. Например, обрыв выдачи и неверный выбор файла требуют разных исправлений, хотя пользователь в обоих случаях видит неработающий интерфейс.

Проверьте, не попала ли статика в SPA fallback

Одностраничное приложение часто отдаёт основной HTML для адресов, которые затем разбирает клиентский маршрутизатор. Это полезно для страницы вида /account/orders, но опасно, если то же правило без разбора обрабатывает отсутствующий /assets/app.js. Браузер получает оболочку приложения вместо программного файла.

Посмотрите, существует ли запрошенный ресурс в опубликованной сборке и совпадает ли публичный путь с настройкой сборщика. Частые направления проверки — неправильный базовый путь, частичная публикация, прежний HTML со ссылкой на удалённый пакет и различие регистра имени. Каждую гипотезу подтверждают конкретным несовпадением адреса и опубликованного файла.

Для исправления разделяют маршруты страниц приложения и статических ресурсов. Отсутствующий скрипт не должен незаметно превращаться в успешный ответ с HTML. При этом нужно сохранить работу настоящих клиентских маршрутов. Поэтому универсальная замена всего конфигурационного блока без знания структуры сайта способна устранить один симптом и сломать другой сценарий.

Само появление корректного ответа об отсутствии файла ещё не означает восстановление интерфейса. Оно лишь делает ошибку публикации видимой. Следующий шаг — обеспечить нужный файл по согласованному адресу или исправить ссылку на совместимый артефакт сборки. Подставлять любой существующий JavaScript с похожим именем нельзя: страница может ожидать другую структуру модулей и функций.

Отделите публичную статику от правил авторизации

Если конечный ответ содержит страницу входа, сравните загрузку в разрешённых состояниях: пользователь авторизован, сессия истекла, посетитель впервые открыл страницу. Правило проверки доступа могло перехватить весь путь, включая файлы, необходимые для самой формы входа. Тогда страница не может корректно запуститься даже для законного посетителя.

Определите, должен ли конкретный ресурс быть публичным. Обычный общий пакет интерфейса и сформированный для пользователя документ имеют разные требования. Нельзя открывать весь каталог только ради устранения MIME-ошибки. Исправляйте правила для согласованного набора ресурсов, сохраняя ограничения доступа к данным и защищённым операциям.

Возможна и промежуточная HTML-страница от защитного сервиса или прокси. Для неё сохраняют идентификатор запроса, время и условия воспроизведения. Проверяют правило, которое выбрало такой ответ, а не назначают всем ответам защитного слоя тип JavaScript. Решение должно обеспечить предусмотренную доставку статики в нужном контексте.

Модульный JavaScript требует подходящего MIME-типа

Руководство MDN по JavaScript-модулям прямо указывает на строгую проверку MIME-типа: модуль должен приходить с JavaScript-совместимым типом, например text/javascript. Для модулей это самостоятельное требование, поэтому удаление nosniff не является способом исправить их загрузку.

Проверьте и файл, подключённый через <script type="module">, и его импорты. Основной модуль может иметь правильный тип, а вложенный файл с расширением .mjs — получать тип по умолчанию. Каждая такая загрузка имеет собственный URL и ответ, которые нужно исследовать отдельно.

Изменение атрибута type в HTML не исправляет неверный ответ сервера. Перевод модуля в обычный скрипт также меняет правила выполнения и не заменяет правильную публикацию. Для новой настройки используйте рекомендованный тип, но не объявляйте любой исторически используемый JavaScript-тип автоматически причиной сбоя: сначала сопоставьте реальный ответ с конкретным сообщением браузера.

Как проверить настройки nginx и Apache

Изменение сервера оправдано после определения источника ответа. Если ресурс формирует приложение или он приходит через прокси, настройка типов локальных файлов может не участвовать в выдаче. Сначала найдите эффективный маршрут, затем изменяйте ответственное правило и проверяйте тот же URL снаружи.

nginx: тип файла и выбор обработчика

Документация nginx описывает types как соответствие расширений MIME-типам, а default_type — как тип по умолчанию. Для локальной статики проверьте подключённую таблицу типов, нужные расширения и переопределения в выбранном контексте. Не заменяйте полную таблицу двумя строками для CSS и JavaScript.

Отдельно проверьте try_files, root и alias: поиск файла зависит от этих настроек, а последний параметр try_files может вести к внутреннему перенаправлению. Если запрос ушёл к HTML приложения, корректировка таблицы расширений не восстановит отсутствующий или неправильно найденный файл. Исправлять следует соответствующую ветвь маршрутизации.

Apache: сопоставление расширений в нужном контексте

В Apache директива AddType задаёт соответствие расширений типу и прежде всего относится к статическим файлам. Для подтверждённой локальной статики допустимое адресное изменение может сопоставить .js и .mjs с text/javascript, а .css — с text/css. Конкретный контекст и разрешения конфигурации проверяет администратор.

Если ответ генерирует приложение, AddType может не влиять на его заголовок. Также проверьте существующие переопределения и обработчики. После изменения сначала проверяют конфигурацию сервера, затем повторяют загрузку затронутых файлов и соседних типов ресурсов. Глобально назначать всему сайту один Content-Type нельзя.

Учтите кеш браузера, CDN и service worker

После исправления источника посетитель может продолжать получать сохранённый неверный ответ. Зафиксируйте, откуда он пришёл: из сети, браузерного кеша, промежуточного кеша или service worker. Доступные признаки зависят от инструментов и инфраструктуры; полезно сравнить тело, заголовки и версию ресурса до и после изменения.

Отключение кеша в инструментах браузера не очищает CDN и не гарантирует обход service worker. Если последний участвует в сайте, отдельно проверьте его маршруты и сохранённые ответы. Он тоже может вернуть HTML для запроса ресурса. Сравнение с чистым профилем помогает локализовать различие, но не заменяет проверку поведения обычных пользователей.

Очищайте или обновляйте конкретный неверный объект после исправления причины. Случайный параметр в адресе иногда меняет кеширование, но не является доказанным ремонтом: инфраструктура может учитывать параметры иначе. Если новый HTML ссылается на новые пакеты, убедитесь в согласованности публикации; если старые страницы ещё используются, предусмотрите доступность требуемых ими ресурсов.

Если проблема воспроизводится только у части посетителей, сохраните различия до массовой очистки кеша. Полезны адрес файла, время, версия страницы и безопасные признаки источника ответа. После удаления всех следов станет сложнее установить, был ли причиной старый HTML, ошибочно сохранённый ответ или разная конфигурация узлов. Исправление следует подтвердить и для новых загрузок, и для ранее открытой страницы в предусмотренном режиме.

Учебный пример: JavaScript-адрес возвращает HTML

Условный сайт shop.example после обновления показывает страницу, но кнопки не работают. В консоли указана ошибка MIME для /assets/app.4d2.js. Следующая последовательность демонстрирует диагностику; адреса, имена файлов и результаты придуманы для примера.

ШагНаблюдениеВывод
URLБраузер запрашивает /assets/app.4d2.jsПроверяем этот точный путь
СтатусПолучен 200Сам по себе успех HTTP не подтверждает выдачу скрипта
ПеренаправленияВнешних переходов нетПродолжаем проверку внутренней обработки
ТелоПришла HTML-оболочка приложенияJavaScript по этому адресу фактически не выдан
ЗаголовкиContent-Type: text/html; присутствует nosniffБраузер обоснованно отвергает ответ как скрипт
ИсточникСборка опубликована под /static/, а /assets/ попадает в fallbackНе совпадают публичный путь и маршрут статики

В этом примере команда исправляет маршрут статики так, чтобы предусмотренный публичный путь /assets/ соответствовал опубликованным пакетам. Она проверяет наличие нужного артефакта и отделяет отсутствующие статические файлы от fallback страниц. После исправления запрос /assets/app.4d2.js возвращает соответствующий файл JavaScript с подходящим типом. Защитный заголовок остаётся включённым.

Если вместо HTML в этом примере изначально находился бы правильный скрипт с text/plain, расследование пошло бы к назначению типа. Именно проверка тела определяет, какую ветвь исправлять. Подмена заголовка без этого шага могла бы замаскировать неправильный маршрут.

Как подтвердить исправление и не пропустить повторение

Проверка должна охватывать исходную страницу и конкретные ресурсы, вызвавшие ошибку. Исчезновение сообщения из консоли после случайного обновления вкладки не доказывает, что исправление дошло до всех нужных путей доставки. Сохраните исходные наблюдения и сравните их с повторной загрузкой.

  • Точный URL отдаёт ожидаемое содержимое и подходящий Content-Type.
  • Нет непредусмотренного перенаправления на вход или HTML-проверку.
  • Сохранён nosniff, а JS-модули и их импорты загружаются корректно.
  • Стили применяются, скрипт выполняется и затронутое действие интерфейса работает.
  • Проверены необходимые состояния авторизации, кеша и вложенные страницы.
  • Отсутствующий ресурс не превращается в ложный успешный HTML-ответ.

Передайте подрядчику компактную запись: время, страница, URL ресурса, цепочка статусов, безопасный фрагмент тела, заголовки и установленный источник ответа. Она позволяет проверить конкретное исправление и сохранить причину проблемы для следующего выпуска.

Подключите внешний контроль HTTP-доступности важных адресов через SiteGuard. Применение стилей, выполнение JavaScript и работу интерфейса после публикации проверяйте отдельно силами команды.