Оптимизируем блог и экономим трафик
Пятница, 19 декабря 2014

Сегодня я расскажу вам, как можно очень сильно ускорить загрузку страниц с большим количеством фотографий (каких довольно много в этом блоге). Секрет прост — WebP. Всё дело в правильной настройке сервера.

Я буду рассказывать на примере Hakyll и nginx, но принцип легко перенести и на другие технологии.

Браузер, когда запрашивает с сервера картинку, ничего не знает о её формате. Не важно, что запрашивается, например, http://example.com/image.png. Такое понятие, как расширение файла, неприменимо в браузере. Оно скорее оставлено для удобства разработки и соответствия файловой системе. Лишь получив от сервера ответ, в заголовке которого будет написано: Content-Type: image/png, браузер может действительно сказать, что пришла картинка в формате PNG и отобразить её. Конечно, в браузерах предусмотрена защита от ошибок, и зачастую браузер угадывает формат ответа, даже если он был сообщён сервером неверно, но факта отсутствия расширений в URL это не отменяет. А значит, мы можем подсовывать WebP-картинку, которая при сходном качестве гораздо лучше сжата, чем PNG или JPG.

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

Для конвертации картинок в WebP можно воспользоваться утилитой, предоставляемой Google. Использовать её нужно примерно так:

cwebp image.jpg -o image.jpg.webp
cwebp -lossless image.png -o image.png.webp

Добавляем генерацию WebP файлов в Hakyll:

webpConverterRules :: Rules ()
webpConverterRules = do
    match "images/**.jpg" $ version "webp" $ do
        route (setExtension "jpg.webp")
        compile $ do
            filePath <- getResourceFilePath
            res      <- unixFilterLBS "cwebp" [filePath, "-mt", "-o", "-"] ""
            makeItem res

    match "images/**.png" $ version "webp" $ do
        route (setExtension "png.webp")
        compile $ do
            filePath <- getResourceFilePath
            res      <- unixFilterLBS "cwebp" [filePath, "-mt", "-lossless", "-q", "100", "-o", "-"] ""
            makeItem res

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

staticFilesRules :: Rules ()
staticFilesRules = do
    match "images/**" $ do
        route   idRoute
        compile copyFileCompiler

Никакой магии.

Переходим ко второй части: настройка сервера. Совсем недавно я использовал самописный сервер на основе Snap Framework. Но от его использования пришлось отказаться из-за крайне неудовлетворительной и негибкой поддержки SSL и TLS. Новый сервер — стандартный nginx, его и будем настраивать.

Во-первых, добавляем новый MIME-тип для WebP-файлов в секцию types:

image/webp webp;

Во-вторых, создаём инструкцию map, которая будет устанавливать значение переменной $webp_suffix в зависимости от заголовка HTTP-запроса Accept:

map $http_accept $webp_suffix {
    default "";
    "~*webp" ".webp";
}

Если в заголовке есть строка webp, то этот формат картинок поддерживается, иначе — нет.

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

location ~* ^/images/.+\.(png|jpg)$ {
    root /home/www-data;
    add_header Vary Accept;
    try_files $uri$webp_suffix $uri =404;
}

Т. е. сначала на диске будет искаться файл с суффиксом $webp_suffix, затем без него. Ну а если и вторая попытка не удалась, то вернётся ошибка 404. Обратите внимание, что если WebP не поддерживается, то $webp_suffix будет равен пустой строке и обе попытки найти подходящий файл будут искать один и тот же файл.

Подробнее о настройке nginx можно прочитать тут.

Вот и всё. Объём отдаваемых данных для поста с фотографиями значительно уменьшился при сходном качестве картинки (png вообще кодируется без потерь). Например, один из последних постов — Гамарджоба, Грузия: часть вторая — имеет размер 13,3 Мб. В случае со включённой оптимизацией этот размер уменьшается до 4,9 Мб.

60% посетителей моего блога используют Google Chrome, а значит, их затронет эта оптимизация. Да и процент пользователей этого браузера в мире один из самых больших, так что выгода очевидна.

← Яндекс.БраузерСанкт-Петербург и Гатчина →

Хочется что-то добавить или сказать? Я всегда рад обсудить. Пишите на me@dikmax.name.