![image](https://habrastorage.org/getpro/habr/post_images/035/b52/25e/035b5225ee23f2aed62beaf2be7de8a8.jpg)
На сайте не применялось никаких динамических механизмов — там было немного анимации, он был создан с применением методов отзывчивого дизайна, но содержимое ресурса практически всегда оставалось неизменным. Автор статьи говорит, что то, что он увидел, быстро проанализировав ситуацию, буквально привело его в ужас. События
DOMContentLoaded
пришлось ждать около 4-х секунд, на полную загрузку страницы ушло 6.8 секунды. В процессе загрузки было выполнено 20 запросов, общий объём переданных данных составил около мегабайта. А ведь речь идёт о статическом сайте. Тут Джонлука понял, что он раньше считал свой сайт невероятно быстрым лишь потому, что привык к гигабитному интернет-соединению с низкой задержкой, используя которое, он, из Лос-Анджелеса, работал с сервером, расположенным в Сан-Франциско. Теперь же он оказался в Италии и воспользовался интернет-соединением на 8 Мбит/с. А это совершенно поменяло картину происходящего.В этой статье Джонлука Де Каро расскажет о том, как ему удалось ускорить свой статический сайт в десять раз.
Обзор
Вот как выглядел сайт, с точки зрения количества запросов, необходимых для его формирования, времени загрузки и объёма загруженных данных, когда стало понятно, что его надо оптимизировать.
![](https://habrastorage.org/getpro/habr/post_images/466/38c/836/46638c83625867d91ef066ac6f8f6c2a.png)
Данные неоптимизированного сайта
Когда я всё это увидел, я впервые занялся оптимизацией своего сайта. До этого момента, если нужно было добавить на сайт библиотеку или что-то ещё, я просто взял бы и загрузил то, что нужно, с помощью конструкции
src="…"
. Я совершенно не обращал внимания на производительность, не думал ни о чём, что может на неё повлиять, включая кэширование, встраивание кода, ленивую загрузку.Тогда я начал искать людей, которые столкнулись с чем-то подобным. К несчастью, публикации, посвящённые оптимизации статических сайтов, очень быстро устаревают. Например, рекомендации, датированные 2010-2011 годами, посвящены использованию библиотек, а в некоторых из них сделаны предположения, касающиеся того, что библиотеками при разработке сайта не пользуются вообще. Анализируя эти материалы, я столкнулся и с тем, что некоторые из них попросту снова и снова повторяют одни и те же наборы правил.
Однако, я всё же нашёл два отличных источника информации: ресурс ?High Performance Browser Networking и публикацию Дэна Луу. Хотя я и не пошёл в деле оптимизации так же далеко, как Дэн, разбирая содержимое сайта и средства его форматирования, мне удалось ускорить загрузку моей страницы примерно в десять раз. Теперь ждать
DOMContentLoaded
приходилось примерно пятую часть секунды, а на полную загрузку страницы было нужно 388 мс (надо сказать, что эти результаты нельзя назвать абсолютно точными, так как на них отразилась ленивая загрузка, о которой речь пойдёт ниже).![](https://habrastorage.org/getpro/habr/post_images/ea8/790/85f/ea879085fa150bd5b81b92a1351a1c9b.png)
Результаты оптимизации
Теперь поговорим о том, как удалось достигнуть таких результатов.
Анализ сайта
Первым шагом процесса оптимизации стало профилирование сайта. Мне хотелось понять, что именно требует больше всего времени, и как лучше всего распараллелить выполнение задач. Я, для профилирования сайта, использовал различные инструменты и проверил то, как сайт загружается из разных мест Земли. Вот список ресурсов, которыми я пользовался:
- https://tools.pingdom.com
- http://www.webpagetest.org/
- https://tools.keycdn.com/speed
- https://developers.google.com/web/tools/lighthouse/
- https://developers.google.com/speed/pagespeed/insights/
- https://webspeedtest.cloudinary.com/
Некоторые из этих ресурсов давали рекомендации по улучшению сайта. Однако, когда для формирования статического сайта требуется несколько десятков запросов, сделать можно очень много всего — от трюков с gif-файлами, пришедших из 90-х годов, до избавления от неиспользуемых ресурсов (я, например, загружал 6 шрифтов, хотя использовал лишь один из них).
![](https://habrastorage.org/getpro/habr/post_images/2bd/7a9/2c9/2bd7a92c912a4e9cae8b5b9d78ba67f9.png)
Временная шкала для сайта, очень похожего на мой. Я вовремя не сделал скриншот для своего сайта, но то, что тут показано, выглядит практически так же как то, что я увидел несколько месяцев тому назад, анализируя свой сайт
Мне хотелось оптимизировать всё, на что я могу повлиять — от наполнения сайта и скорости выполнения скриптов, до настроек веб-сервера (Nginx в моём случае) и DNS.
Оптимизация
?Минификация и объединение ресурсов
Первым, что я заметил, было то, что страница выполняет множество запросов на загрузку CSS и JS-файлов (при этом не использовались постоянные HTTP-соединения), которые ведут к различным ресурсам, работа с некоторыми из которых велась по HTTPS. Всё это означало, что ко времени загрузки данных добавлялось время, необходимое для прохождения многочисленных запросов к сетям доставки контента или к обычным серверам, и для получения ответов от них. При этом некоторые JS-файлы запрашивали загрузку других файлов, что и приводило к ситуации каскадов блокировок, показанной на рисунке выше.
Для того чтобы объединить всё необходимое в один JS-файл, я использовал webpack. Каждый раз, когда я вносил какие-то изменения в содержимое страницы, webpack автоматически минифицировал и собирал все зависимости в один файл. Вот содержимое моего файла
webpack.config.js
:const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const ZopfliPlugin = require("zopfli-webpack-plugin");
module.exports = {
entry: './js/app.js',
mode: 'production',
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
},
module: {
rules: [{
test: /\.css$/,
loaders: ['style-loader', 'css-loader']
}, {
test: /(fonts|images)/,
loaders: ['url-loader']
}]
},
plugins: [new UglifyJsPlugin({
test: /\.js($|\?)/i
}), new ZopfliPlugin({
asset: "[path].gz[query]",
algorithm: "zopfli",
test: /\.(js|html)$/,
threshold: 10240,
minRatio: 0.8
})]
};
Я экспериментировал с различными вариантами, в итоге у меня получился один файл
bundle.js
, блокирующая загрузка которого выполняется в разделе <head>
моего сайта. Размер этого файла составлял 829 Кб. Сюда входило всё кроме изображений (шрифты, CSS, все библиотеки и зависимости, а также мой JS-код). Большая часть этого объёма, 724 из 829 Кб, приходилась на шрифты font-awesomе.Далее, я поработал с библиотекой Font Awesome и убрал оттуда всё, кроме трёх иконок, которые использовал: ?
fa-github
, fa-envelope
, и fa-code
. Для того чтобы извлечь из библиотеки только нужные мне иконки, я использовал сервис fontello. В результате мне удалось сократить размер загружаемого файла всего до 94 Кб.То, как спроектирован сайт, не позволяет вывести его правильно, если браузер обработает только HTML и CSS, поэтому я не пытался бороться с блокирующей загрузкой файла
bundle.js
. Время загрузки составило примерно 118 мс, что было более чем на порядок лучше, чем раньше.У такого подхода обнаружились и несколько дополнительных полезных свойств. Так, мне теперь не приходилось обращаться к сторонним серверам или сетям доставки контента, в результате, когда моя страница загружалась в браузер, система освобождалась от выполнения следующих задач:
- Выполнение DNS-запросов к сторонним ресурсам.
- Выполнение процедур установки HTTP-соединений.
- Выполнение полной загрузки необходимых данных.
В то время как использование CDN и распределённое кэширование могут иметь смысл для масштабных веб-проектов, мой маленький статический сайт от использования CDN-ресурсов ничего не выигрывает. Отказ от них, ценой некоторых усилий с моей стороны, позволил дополнительно сэкономить что-то около сотни миллисекунд, что, на мой взгляд, оправдывает эти усилия.
?Сжатие изображений
Продолжая анализ сайта, я обнаружил, что на страницу загружался мой 8-мегабайтный портрет, который выводился в размере, составляющем 10% по ширине и высоте от его исходного размера. Это — не просто недостаточная оптимизация. Такие вещи демонстрируют почти полное безразличие разработчика к использованию полосы пропускания интернет-канала пользователя.
![](https://habrastorage.org/getpro/habr/post_images/128/a79/110/128a79110915daa7658043a697577d4f.png)
Оптимизация фотографии
Я сжал все используемые на сайте изображения, используя https://webspeedtest.cloudinary.com/. Там мне была дана рекомендация использовать формат webp, но мне хотелось, чтобы моя страница была совместима с как можно большим количеством браузеров, поэтому я остановился на формате jpeg. Можно настроить всё так, чтобы webp-изображения использовались только в браузерах, которые этот формат поддерживают, но я стремился к максимальной простоте, и решил, что выгоды от дополнительного уровня абстракции, связанного с изображениями, не стоят усилий, затраченных на достижение этих выгод.
?Улучшение веб-сервера: HTTP2, TLS и кое-что ещё
Приступив к оптимизации сервера, я сразу же перешёл на HTTPS. В самом начале я использовал обычный Nginx на 80-м порту, который просто обслуживал файлы из
/var/www/html
. Вот код моего исходного файла nginx.conf
.server{
listen 80;
server_name jonlu.ca www.jonlu.ca;
root /var/www/html;
index index.html index.htm;
location ~ /.git/ {
deny all;
}
location ~ / {
allow all;
}
}
Итак, я начал с настройки HTTPS и с перенаправления всех HTTP-запросов на HTTPS. Я получил TLS-сертификат от Let’s Encrypt (эта замечательная организация, кроме того, недавно начала подписывать и wildcard-сертификаты). Вот обновлённый
nginx.conf
.server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name jonlu.ca www.jonlu.ca;
root /var/www/html;
index index.html index.htm;
location ~ /.git {
deny all;
}
location / {
allow all;
}
ssl_certificate /etc/letsencrypt/live/jonlu.ca/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/jonlu.ca/privkey.pem; # managed by Certbot
}
Благодаря добавлению директивы
http2
, Nginx смог использовать полезнейшие функции новейших возможностей HTTP. Обратите внимание на то, что для того, чтобы пользоваться HTTP2 (ранее это называлось SPDY), необходимо применять HTTPS. Подробности о этом можно почитать здесь. Кроме того, с помощью конструкций вида http2_push images/Headshot.jpg;
можно воспользоваться push-директивами HTTP2.Обратите внимание на то, что использование gzip и TLS повышает риск BREACH-атаки на веб-ресурс. В моём случае, так как речь идёт о статическом сайте, риск подобной атаки весьма низок, поэтому я спокойно пользуюсь сжатием.
?Использование директив кэширования и сжатия
Что ещё можно сделать, пользуясь лишь Nginx? Первое, что приходит в голову — использование директив кэширования и сжатия.
Раньше мой сервер отдавал клиентам обычный, несжатый HTML. Воспользовавшись единственной строкой
gzip: on;
, я смог уменьшить размер передаваемых данных с 16000 байт до 8000, а это означает уменьшение их объёма на 50%.На самом деле, этот показатель можно улучшать и дальше, если воспользоваться директивой
Nginx gzip_static: on;
, что позволит системе ориентироваться на использование предварительно сжатых версий файлов. Это согласуется с вышеприведённой конфигурацией webpack, в частности, для того, чтобы организовать предварительное сжатие всех файлов во время сборки, можно воспользоваться ZopfliPlugin. Это экономит вычислительные ресурсы и позволяет максимизировать сжатие, не теряя в скорости.Кроме того, мой сайт меняется довольно редко, поэтому я стремился к тому, чтобы его ресурсы кэшировались бы на как можно более длительный срок. Это привело бы к тому, что, посещая мой сайт несколько раз, пользователь не нуждался бы в повторной загрузке всех материалов (в особенности —
bundle.js
).Вот к какой конфигурации сервера (
nginx.conf
) я в итоге пришёл.worker_processes auto;
pid /run/nginx.pid;
worker_rlimit_nofile 30000;
events {
worker_connections 65535;
multi_accept on;
use epoll;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Turn of server tokens specifying nginx version
server_tokens off;
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
include /etc/nginx/mime.types;
default_type application/octet-stream;
add_header Referrer-Policy "no-referrer";
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_dhparam /location/to/dhparam.pem;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
ssl_certificate /location/to/fullchain.pem;
ssl_certificate_key /location/to/privkey.pem;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;
gzip_min_length 256;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Обратите внимание на то, что тут не затронуты все ранее выполненные улучшения, касающиеся настроек TCP, директив gzip и кэширования. Если вы хотите узнать обо всём этом подробности — взгляните на этот материал, посвящённый настройке Nginx.
А вот блок настройки сервера из моего
nginx.conf
.server {
listen 443 ssl http2;
server_name jonlu.ca www.jonlu.ca;
root /var/www/html;
index index.html index.htm;
location ~ /.git/ {
deny all;
}
location ~* /(images|js|css|fonts|assets|dist) {
gzip_static on; # Tells nginx to look for compressed versions of all requested files first
expires 15d; # 15 day expiration for all static assets
}
}
?Ленивая загрузка
И, наконец, я внёс в сайт небольшое изменение, которое способно было значительно улучшить положение дел. На странице есть 5 изображений, которые можно увидеть, лишь щёлкая по соответствующим им миниатюрам. Эти изображения загружались во время загрузки всего остального содержимого сайта (причина тут в том, что пути к ним находились в тегах
<img align="center" src="…">
).Я написал небольшой скрипт для модификации соответствующего атрибута каждого элемента с классом
lazyload
. В результате эти изображения теперь загружаются лишь при щелчке по соответствующим им миниатюрам. Вот как выглядит файл lazyload.js
:$(document).ready(function() {
$("#about").click(function() {
$('#about > .lazyload').each(function() {
// установка src для элемента img на основании data-src
$(this).attr('src', $(this).attr('data-src'));
});
});
$("#articles").click(function() {
$('#articles > .lazyload').each(function() {
// установка src для элемента img на основании data-src
$(this).attr('src', $(this).attr('data-src'));
});
});
});
Этот скрипт обращается к элементам
<img>
, устанавливая <img src="…">
на основании <img data-src="…">
, что позволяет загружать изображения тогда, когда они нужны, а не во время загрузки основных материалов сайта.Дальнейшие улучшения
У меня есть на примете ещё несколько улучшений, которые могут повысить скорость загрузки страницы. Пожалуй, самые интересные из них — это использование сервис-воркера для перехвата сетевых запросов, что позволит сайту работать даже без подключения к интернету, и кэширование данных средствами CDN, что позволит пользователям, расположенным далеко от моего сервера в Сан-Франциско, сэкономить время, необходимое на обращение к нему. Это ценные идеи, однако они не особенно важны в моём случае, так как речь идёт о небольшом статическом сайте, играющем роль онлайнового резюме.
Итоги
Вышеописанные оптимизации позволили уменьшить время загрузки моей страницы с 8 секунд до примерно 350 миллисекунд при первом обращении к ней, и, при последующих обращениях, до просто невероятных 200 миллисекунд. Как видите, для достижения подобных улучшений потребовалось не так уж и много сил и времени. И, кстати, тем, кто заинтересован в оптимизации веб-сайтов, рекомендую почитать вот это.
Уважаемые читатели! Как вы оптимизируете свои сайты?
![](https://habrastorage.org/files/1ba/550/d25/1ba550d25e8846ce8805de564da6aa63.png)
Комментарии (17)
Loki3000
26.03.2018 14:57+3Какое-то странное впечатление от статьи. Автор с таким воодушевлением рассказывает о банальных вещах… Типа «Оказывается, надо мыть руки перед едой! Да-да, и еще чистить зубы утром и вечером!». Неужели все эти знания уже попали в разряд таких древностей, что их обнаружение вызывает подобные восторг?
barbanel
26.03.2018 15:57Есть вероятность что таки да, попали.
Каналы все толще, компы все мощнее, программеры все ленивее.
"+100 мс? Да пофиг, я моргаю дольше. Комп вытянет."
«20 Мб весят картинки сайта? Да пофиг, хром всерно полтора гига при старте отжирает».
«Сайт личный, не high load? Ну, вы поняли».
Лень приводит к нежеланию разбираться в причинах, систематическое нежелание — к забыванию мониторить все больше и больше, забывание — к забыванию…
khanid
26.03.2018 23:55Такие очевидные, на первый взгляд, вещи многим людям, как выясняется, совсем неочевидны.
Я помню боролся с командой веб-разработки у себя. Они сетовали, де сервер слабый, nginx медленный, каналы худые и не выдерживают нагрузки. Пока не ткнул пальцем вот сюда и сюда, что главная в 30+ Мб (из которых одних js 12 Мб) — так себе решение — никто не чесался.
Причина, как уже сказал выше уважаемый barbanel в, скорее всего, лени и нежелании разбираться. Ёж птица гордая…
yzoz
27.03.2018 01:00Неужели все эти знания уже попали в разряд таких древностей, что их обнаружение вызывает подобные восторг?
Ну таки вот выходит, что да… Мне начало понравилось своей многообещающей-стью, но по ходу дела стало быстро ясно, что автор из того нового класса разработчиков, что со старта сидя на гигабитном канале, действительно не задумываются о многом.
В любом случае похвально, что теперь задумался и всё поправил.
+ несколько полезных ссылок подкинул.
Vlad_fox
26.03.2018 16:27краткое содержание — 1. я слепил замок из песка, вроде прикольно.
2. друг сказал что не очень, книжку нашел — архитектура для чайников.
профит — подправил свой замок — стало в 10 раз лучше.
apapacy
26.03.2018 16:33Суть статьи в этой фразе
что привык к гигабитному интернет-соединению с низкой задержкой, используя которое, он, из Лос-Анджелеса, работал с сервером, расположенным в Сан-Франциско. Теперь же он оказался в Италии
С этими рекомендациями автор отстал лет на 10. После того как ЯваСкрипт кода стало больше на повсетке дня не code bandling (который давно и всеми решен), а code splitting.
Особенно радует что теперь html будет на просто так рапространяться а по протоколу http2.TheShock
27.03.2018 07:13Зачем сплитить 100 КБ кода? Только потому что это сейчас можно, а не 10-тилетняя давность?
Senpos
26.03.2018 17:29Интересный сервис по оптимизации картинок, ссылку забрал, спасибо.
Проверил один из тестовых сайтов, результат впечатлил: пользователь смартфона при переходе на главную страницу загружал 4.1 мегабайта картинок, вместо 98 килобайт.
capslocky
26.03.2018 18:41Добавлю еще 3 инструмента анализа, которые мне были полезны:
https://gtmetrix.com
https://www.dareboost.com
http://yslow.org
kartvladek
27.03.2018 14:49Печально, что люди делают статичные сайты так, что потом требуется оптимизировать их таким вышеупомянутым способом. Удивлен, признаюсь.
kentov
27.03.2018 17:27Хоть и скорости сейчас высоки и компы мощнее, в любом случае чемы быстрее отвечает и рендерится ваш сайт, тем больше шансов обогнать конкурентов.
TimsTims
Если посетителем сайта был только 1 админ, который заходил со своего же сервера, то смысл было оптимизировать сайт?
Сам загрузил своюА иначе, неужели никто раньше не жаловался, что сайт грузится медленно (хотя бы владельцы-заказчики)? Неужели админ не интересовался как грузится его сайт? Впервые вырвался из своей норы, а попробовать включить throtting, или открыть свой же сайт через 3g не догадался?
прыщавуюфотку, а признаться, что сфейлил — не хватило духа. Мог бы и прямо сказать: я так лажанулся, загрузив 8мб фотку, что проявил неуважение к своим пользователям. Вместо этого много слов про некоего разработчика, который во всем виноват.TheIseAse
Под разработчиком он подразумевает себя (: