От автора: пару месяцев назад я выехал за пределы США и хотел показать другу ссылку на моем личном (статичном) сайте. Я попытался перейти на свой сайт, но это заняло гораздо больше времени, чем я ожидал. Он не содержит ничего динамичного — только анимацию и адаптивный дизайн, но контент всегда остается прежним. Я был поражен результатами, ~ 4 секунды до DOMContentLoaded и 6.8 секунд до полной загрузки страницы. Было выполнено 20 запросов -1 мб всех переданных данных. Я в Лос-Анджелесе привык к гигабитному интернету с низкой задержкой, что заставляет даже этого монстра грузиться очень быстро. Но в Италии, на 8 Мб / с была совсем другая картина.
В результате я впервые задумался об оптимизации. До этого момента всякий раз, когда я хотел добавить библиотеку или ресурс, я просто забрасывал ее и указывал с помощью src = «…». Я вообще не задумывался ни о кэшировании, ни об отложенной загрузке, ни о чем другом — оптимизация производительности вообще не занимала моих мыслей.
После этого я начал искать людей с похожим опытом. К сожалению, большая часть литературы по оптимизации статических сайтов датируется довольно отдаленными временами — рекомендации от 2010 или 2011 годов рассматривали библиотеки или делали предположения, которые просто больше не верны. Или не содержали ничего существенного.
Тем не менее, я нашел два отличных источника информации — High Performance Browser Networking и опыт Дэна Лю по оптимизации статических сайтов. Хотя я не заходил так далеко, как Дэн в форматировании и оптимизации контента, мне удалось уменьшить время загрузки страницы примерно в 10 раз, где-то одна пятая секунды до DOMContentLoaded и всего 388 мс до полной загрузки страницы (что на самом деле немного неточно, так как это включает отложенную загрузку, описанную ниже).
Процесс
Первым шагом было профилирование сайта. Я хотел разобраться в том, что было самым накладным, и как лучше всего распараллелить все. Я использовал различные инструменты для профилирования сайта и тестирования его из разных точек по всему миру, в том числе:
https://tools.pingdom.com/
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/
Некоторые из них предложили рекомендации по улучшению, но, когда ваш статический сайт выполняет 50 запросов, фронт работ очень большой — на gif слева показано все, что осталось от прежних 90 ресурсов, которыми я не пользовался (у меня загружалось 6 шрифтов и только 1 из них использовался).
Временная шкала для моего сайта — я тестировал его в Web Archive но того скриншота у меня нет. Данный снимок экрана выглядит довольно похоже на то, что я видел несколько месяцев назад.
Я хотел улучшить все, что возможно — от контента и скорости javascript до фактического веб-сервера (Nginx) и настроек DNS.
Оптимизации
Минимизация и объединение ресурсов
Первое, что я заметил, было то, что у меня выполнялось до десятка запросов для CSS и JS и к разным сайтам, некоторые из которых были на https. Это добавляло несколько обращений к различным CDN или серверам, а некоторые JS-файлы запрашивали другие, что вызввало блокирующий каскад, рассмотренный выше.
Я использовал webpack для объединения всех ресурсов в один файл 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). Подавляющее большинство из них — шрифты font-awesome, которые составляют 724 из 829Кb.
Я просмотрел шрифты font-awesome и css, и убрал все, кроме трех иконок, которые использовал: fa-github, fa-envelope и fa-code. Я использовал сервис под названием fontello, чтобы вытащить нужные мне иконки. Новый размер — 94Kb.
Исходя из способа построения сайта, он не смог бы работать корректно, если бы я использовал только таблицы стилей, поэтому я смирился с блокирующим характером одного пакета bundle.js. Время его загрузки составляет ~ 118 мс, что на порядок выше, чем было раньше.
Это также давало мне несколько дополнительных преимуществ — я больше не указывал на сторонние ресурсы или CDN, поэтому пользователю не нужно было: 1) выполнять DNS-запрос к этому ресурсу; 2) выполнять https-рукопожатия; 3) ждать полной загрузка с этого ресурса.
Хотя CDN и распределенное кэширование могут иметь смысл для больших сайтов, для моего небольшого статического сайта это было не нужно. Дополнительные сто миллисекунд или около того — это выгодный компромисс.
Сжатие ресурсов
Я загружал фото размером в 8 Мб, а затем отображал его в размерах в 10% от фактических. Это было не просто отсутствие оптимизации — это было почти ненадлежащее использование пропускной способности пользователей.
Я сжал все изображения с помощью https://webspeedtest.cloudinary.com/ — также думал переключиться на webp, но я хотел оставаться совместимым с таким количеством браузеров, какое было только возможно, поэтому оставил jpg. Можно настроить систему, в которой webp доставляется только в браузеры, которые его поддерживают, но я хотел оставить все максимально просто, и преимущества добавленного этого слоя абстракции не выглядели стоящими затрат.
Улучшение веб-сервера — HTTP2, TLS и многое другое
Первое, что я сделал, это перешел на https — ранее у меня запускался Nginx через порт 80, который просто давал просматривать файлы /var/www/html
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 (отличная организация, которая недавно начала подписывать вайлкард-сертификаты!).
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 с http2_push images/Headshot.jpg;
Примечание. Включение gzip и TLS может нести потенциальную угрозу для BREACH. Поскольку это статический сайт, и фактические риски для BREACH очень малы, я чувствовал себя комфортно, сохраняя сжатие.
Использование директив кэширования и сжатия
Что еще можно сделать с Nginx? Первое, что приходит на ум — директивы кэширования и сжатия. Я отправлял сырые, несжатые HTML. Только с помощью простого gzip; line, я смог уменьшить объем с 16000 байт до 8000 байт — это на 50% меньше.
На самом деле мы можем еще уменьшить это число — если в Nginx установлен Gzip_static, он будет искать предварительно сжатые версии всех запрошенных файлов. Это перекликается с конфигурацией webpack, упомянутой выше — мы можем использовать ZopflicPlugin для предварительного сжатия всех файлов во время сборки! Это экономит вычислительные ресурсы и позволяет максимизировать сжатие без потерь ускорения.
Кроме того, мой сайт изменяется довольно редко, поэтому я хотел, чтобы ресурсы кэшировались как можно на дольше. Это сделало бы так, чтобы при последующих посещениях пользователям не нужно было повторно загружать все ресурсы (особенно bundle.js).
Моя обновленная конфигурация сервера выглядит так. Обратите внимание, что я не описываю все изменения, такие как изменения настроек TCP, директивы gzip и кеш файлов.
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/*; }
И соответствующий серверный блок
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 src = «…»>). Я написал короткий скрипт, чтобы изменить атрибут для каждого изображения и добавить класс lazyload.
$(document).ready(function() { $("#about").click(function() { $('#about > .lazyload').each(function() { // set the img src from data-src $(this).attr('src', $(this).attr('data-src')); }); }); $("#articles").click(function() { $('#articles > .lazyload').each(function() { // set the img src from data-src $(this).attr('src', $(this).attr('data-src')); }); }); });
Таким образом, теперь, как только документ будет загружен, скрипт изменяет теги img, чтобы они преобразовывались из <img data-src = «…»> в <img src = «…»> и загружались в фоновом режиме.
Дальнейшие улучшения
Есть еще несколько изменений, которые могли бы повысить скорость загрузки страницы — в первую очередь использование Service Workers для кэширования и перехвата всех запросов, а также для работы сайта даже в автономном режиме и кэширования содержимого на CDN, чтобы пользователям не нужно было полностью переходить на сервер в SF. Это целесообразные изменения, но не особенно важные для личного статического сайта, который выполняет роль онлайн-резюме / страницы обо мне.
Заключение
Это все позволило уменьшить время загрузки страницы с более чем 8 секунд до ~ 350 мс при загрузке первой страницы, и безумные ~ 200 мс при последующих. Я действительно рекомендую вам прочитать обо всех советах на High Performance Browser Networking — это довольно доступно и дает возможность обеспечить невероятно высокую оптимизацию на каждом уровне современной интернет-модели.
Автор: JonLuca De Caro
Источник: https://hackernoon.com/
Редакция: Команда webformyself.