Сразу отвечу: тег script лучше ставить в конце body
- так загружается контент, а потом уже скрипты. Но всё не так просто: в зависимости от задачи и требований к скорости загрузки могут пригодиться варианты в head
, атрибуты async
и defer
, а также модульные скрипты.
script - это HTML‑элемент, позволяющий подключать или писать JavaScript‑код, который будет выполнен в браузере пользователя. Он может загружать внешний файл через атрибут src
или содержать встроенный код между открывающим и закрывающим тегами. Скрипты управляют интерактивностью, отправкой форм, анимацией, динамической подгрузкой данных и многим другим.
head
- скрипт загружается до отрисовки любой части страницы. Это удобно, когда нужен ранний доступ к глобальным переменным или функции, вызываемые сразу при парсинге HTML.body
- самый распространённый вариант. Браузер сначала обработает весь контент, а потом выполнит скрипт, что ускоряет визуальную загрузку.Размещение в head
без атрибутов заставляет браузер блокировать рендеринг до полной загрузки и выполнения скрипта. Поэтому в современных проектах стараются избегать такого поведения, используя async
или defer
.
async
: загрузка без блокировкиКогда к тегу script
добавлен async
, браузер начинает скачивать файл параллельно с построением DOM‑дерева. Как только скрипт готов, он сразу же исполняется, даже если страница ещё не полностью построена. Это полезно для небольших аналитических или рекламных скриптов, которые не влияют на работу основного контента.
Недостаток - отсутствие гарантии порядка выполнения, если их несколько. Если один скрипт зависит от другого, async
может привести к ошибкам.
defer
: отложенный запускС defer
браузер также скачивает файл параллельно, но откладывает исполнение до полного построения DOM и события DOMContentLoaded
. Скрипты с defer
сохраняют порядок, прописанный в документе, что делает их безопасным выбором для большинства внешних библиотек.
Важно: атрибут defer
работает только с внешними скриптами (src
), встроенный код игнорирует его.
type="module"
)С появлением ES‑модулей в браузерах появился новый способ организации кода. Тег script
с атрибутом type="module"
автоматически получает поведение defer
- скрипт загружается асинхронно и исполняется после построения DOM. Кроме того, модули поддерживают import
и export
, позволяют разбивать код на файлы без глобального пространства имён.
Если ваш проект уже использует сборщики типа Webpack, Rollup или Vite, модульные скрипты - естественное продолжение.
Google Lighthouse и PageSpeed Insights измеряют метрики, такие как First Contentful Paint (FCP) и Time to Interactive (TTI). Блокирующие скрипты в head
резко ухудшают эти показатели, потому что пользователь видит пустой экран дольше.
Расположение в конце body
или использование defer
/async
сокращает время до первого отрисованного элемента. По данным Web.dev, правильное применение defer
может ускорить TTI на 30‑40 %.
DOMContentLoaded
и какой код его задерживает.body
или добавить defer
.async
в head
или в конце body
.defer
в head
или обычный script
в конце body
.type="module"
, размещать в head
без опасений блокировки.body
без атрибутов.head
, если только они не критичны для начального рендеринга.Ошибка | Последствия | Решение |
---|---|---|
Блокирующий script в head | Значительно замедленный рендеринг, плохие метрики | Добавить defer или перенести в конец body |
Несколько async ‑скриптов с зависимостями | Непредсказуемый порядок выполнения, ошибки «undefinded» | Перейти на defer или объединить в один модуль |
Встроенный скрипт без defer в head | Блокировка парсинга, срыв CSS‑адаптивности | Переместить код в конец body или вынести в отдельный файл с defer |
Отсутствие атрибута type="module" у модульных файлов | Браузер считает их обычными скриптами, ломается импорт | Указать type="module" и использовать defer по умолчанию |
Допустим, у вас на странице три скрипта:
lodash.js
(независимая утилита).analytics.js
(отправка статистики).app.js
, который использует lodash
и зависит от DOM.Оптимальный шаблон HTML будет выглядеть так:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Пример оптимального размещения скриптов</title>
<script src="/js/lodash.js" defer></script>
<script src="/js/analytics.js" async></script>
</head>
<body>
<!-- Содержимое страницы -->
<script src="/js/app.js" defer></script>
</body>
</html>
Здесь lodash
и app
получат отложенный запуск после парсинга DOM, а аналитика загрузится независимо и не будет мешать пользователю.
Выбор места для тег script зависит от того, насколько критичен код для начального рендеринга и есть ли у него зависимости. Правильное использование async
, defer
и модульных скриптов помогает сохранить быстрый First Contentful Paint и улучшить пользовательский опыт.
Да, браузер выполнит их в том порядке, в каком они указаны в документе. Это удобно для библиотек, которые зависят друг от друга.
async загружает скрипт параллельно и исполняет его сразу после загрузки, без ожидания построения DOM. defer тоже загружает параллельно, но откладывает исполнение до события DOMContentLoaded и сохраняет порядок.
Нет. При указании type="module"
скрипт автоматически получает поведение defer, поэтому отдельный атрибут не нужен.
Откройте DevTools → Network, включите колонку «Blocking» и смотрите, какие запросы имеют статус «blocking». Также обратите внимание на метрику «Render‑Blocking Resources» в Lighthouse.
Нет, эти атрибуты взаимоисключающие. Если указать оба, браузер выберет один из них (обычно defer), но это считается плохой практикой.