
Ты понимаешь, что баг хорош, если первым делом думаешь: «Как это вообще может происходить?»
Недавно я занимался доработкой дэшборда веб-приложения, над которым мы работаем, и заметил, что его загрузка длится бесконечно. Раньше на это требовалась одна секунда, но теперь ему требуется десять. Происходит что-то подозрительное.
Разумеется, сначала я обвинил в этом React.
Да, конечно, в современном веб-приложении может быть множество потенциальных причин проблем с производительностью: сторонний JavaScript, перегруженные серверы, раздутые ресурсы, отсутствующие индексы базы данных; список можно продолжать очень долго. Но опыт десятков лет разработки для веба подсказывал мне, что это была проблема фронтенда. Я просто чувствовал это. При загрузке страница дёргалась. И несмотря на то, что экосистема React сегодня — наименее плохой выбор для веб-фронтенда, она по разным причинам может оказаться хаотичной и тормозной.
Чтобы подтвердить свою теорию, я рассказал Claude1, что загрузка дэшборда тормозит, и что проблемы наверняка в React, попросив проанализировать их и ранжировать по степени серьёзности. Конечно же, Claude обнаружил в React кучу подозрительных аспектов — ненужный повторный рендеринг, отсутствующие мемоизации и так далее. К тому же он подсказал, что мы всё ещё не используем React Compiler. Я попросил Claude выполнить первый проход решения самых простых и самых серьёзных проблем React, но…
Практически ничего не поменялось. Возможно, дело всё-таки не в React.
Итак, я закатал рукава и начал тщательное расследование.
Может быть, на самом деле тормозит сервер? Есть немного, но это не мешает фронтенду.
Возникает ли эта проблема во всех браузерах? Нет. Она почему-то специфична для Safari?
А, наверно, тогда это сторонний JavaScript. Intercom? Нет. PostHog? Нет.
Ладно, пора пристальнее изучить хронологию производительности.
Пути инспекторов производительности Safari и Chromium за несколько лет сильно разошлись, и первый стал довольно нестабильным (по крайней мере, на этой странице). Но он демонстрировал достаточно чёткую картину: браузер не тратил семь с лишних секунд на парсинг JavaScript, вычисление стилей или загрузку из сети. Он использовал 94% процессора M1 Max на… компоновку (Layout)?

При подробном изучении выяснилось, что многие проходы Layout занимали по 1600 мс каждый. Для справки: это примерно в сто раз больше, чем должно быть, а значит, в компоновке страницы явно творится что-то не то. Flexbox может быть немного медленным, но не настолько.
Пора приступать к разборке
На этом этапе я воспользовался древним инструментом, который стал ещё более полезным в современности: двоичным поиском. То есть я объяснил симптом кодинг-агенту, многократно просил его удалить из кода элементы, которые могут вызывать проблему, и наблюдал, пропадает ли она. Когда я нашёл что-то, решающее проблему, то итеративно добавил всё обратно, пока не получил минимальное изменение, указывающее на причину, а значит, и на способ её обхода.
Особенно быстр этот процесс, если агент сам видит проблему, но у меня не было под рукой инструмента анализа производительности Safari для командной строки. Поэтому я сам говорил Claude, устранило ли изменение проблему, и обучал его рассуждать о том, что мы узнавали на каждом шаге. И спустя всего десять минут нам наконец удалось обнаружить виновника!
Это было эмодзи сердца. ❤️
Если удалить эмодзи с кнопки «Отправить отзыв» (которую я недавно добавил), то Safari начинает создавать компоновку страницы меньше, чем за 2 миллисекунды. Если вернуть сердце обратно, то странице требуется по 1600 мс на каждую компоновку, а их было много.
Мне нравится использовать эмодзи в прототипах интерфейсов. Их легко добавлять, и они загружаются быстрее, чем картин��и. Или нет? Один символ шрифта не должен замедлять рендеринг в сто раз по сравнению с остальной частью динамического веб-приложения React. Похоже, мы обнаружили баг Safari.
Когда находишь нечто похожее на баг браузера, то появляется желание отправить о нём отчёт. Однако для отправки бага нельзя просто приложить к отчёту весь свой проект. Необходимо создать минимальный пример воспроизведения: «Вот простой файл, вызывающий проблему. Загрузите его и посмотрите сами». Кроме того, это означает, что вы полностью разобрались в проблеме и, скорее всего, найдёте решение получше, чем никогда больше не использовать эмодзи в приложении.
Однако создание минимального образца для воспроизведения — это огромный барьер для отправляющих отчёты, ведь чтобы свести полнофункциональное приложение, содержащее проприетарные части, к минимальному примеру воспроизведения, требуется куча кропотливой работы.
Или требовалась раньше! Кодинг-агенты не только способны изолировать баги, но и замечательно подходят для создания минимальных тестовых случаев. Они могут редактировать большие объёмы кода за раз, и обычно для этого не нужно особой изобретательности, достаточно итеративно удалять максимальное количество кода, пока это не мешает срабатыванию бага.
Итак, довольно быстро я получил очень простой пример воспроизведения для команды разработчиков. Но после изучения кода минимального воспроизведения становится очевидно, кто же настоящий виновник. На моём Mac браузеру Safari 26.2 требуется 1600 мс для создания компоновки следующего HTML.
<!DOCTYPE html> <html> <head> <link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji" rel="stylesheet"> <style> body { font-family: "Noto Color Emoji"; } </style> </head> <body> 💔 </body> </html>
Будь ты проклят, Noto Color Emoji.
Сегодня у шрифтов могут быть цвета!
Раньше шрифты были просто фигурами. Они могли иметь чёрный, белый или любой другой цвет, какой выберешь. Но в 2008 году Apple выпустила в iPhone OS 2.2 Apple Color Emoji, а в 2011 году перенесла его в Mac OS X. Рост популярности эмодзи вызвал потребность в шрифтах, имеющих собственный цвет.
Изначально Apple Color Emoji был хаком. Компания просто засунула в шрифт PNG-изображения; он не был ни стандартизированным, ни независимым от разрешения. При увеличении размера он начинал выглядеть ужасно. Из-за этого появилось четыре конкурирующих стандарта цветных шрифтов (от Apple, Mozilla, Google и Microsoft); все они были добавлены OpenType 1.7. Согласно Википедии, Microsoft и Apple добавила поддержку этих решений, и на этом история вроде бы закончилась.
Но, как это часто бывает, она на этом не закончилась.
Дело в том, что Noto Color Emoji — это шрифт Google, полезный тем, что обеспечивает согласованный рендеринг эмодзи на разных платформах. Мы добавили его, чтобы обеспечить качественный рендеринг эмодзи в Linux (где выполняем облачный рендеринг из HTML в видео — звучит пугающе, но иногда это может быть довольно полезным). Однако этот шрифт использует COLRv1 — спецификацию, которая, по словам Google, позволит приложениям загружаться быстрее, потому что благодаря ей эмодзи становятся меньше, чем растровые изображения; в других браузерах она откатывается к поддержке SVG2.
В данном случае, «другие браузеры» — это Safari. И, наверно, под «откатом к SVG» подразумевается трата 1600 мс на компоновку одного символа. Если вы хотите посмотреть, как это выглядит в больших масштабах, то попробуйте загрузить на iPhone страницу Google Fonts, пытающуюся показать все глифы Noto Color Emoji . (В iOS 26.2 результаты оказываются печальными.)

Когда я написал об этом баге в Slack, Дэниел Джелкат отправил его в баг-трекер Safari. Саймон Фрейзер из команды webkit уже ответил на отчёт, сообщив, что торможения, вероятно, возникают внутри CoreSVG. Есть вероятность, что проблему устранят!
А пока я хочу добавить свою скромную находку в поисковый корпус: не используйте Noto Color Emoji на платформах Apple — первым выберите «Apple Color Emoji». По крайней мере, пока баг не устранят, и релиз Safari с исправлением не получит широкого распространения3.
Ещё мне нужно поделиться небольшим секретом. Как бы ни был полезен Claude в отладке — я точно устранил эту проблему в десять раз быстрее, чем без него, но именно Claude осведомил нас о существовании Noto Color Emoji. Подозреваю, что без кодинг-агента мы решили бы проблему с эмодзи в Linux более скучным способом (при помощи библиотеки иконок) и не стали бы реализовывать эти странные медленные эмодзи.
Похоже, с каждым месяцем становится всё очевиднее: эти кодинг-агенты похожи на бензопилу. Невероятно полезную и невероятно опасную.
Итак, да здравствует Claude! Причина и решение проблем всех стартапов.
На момент публикации я обычно пользуюсь Claude 4.5 Opus в Cursor или Claude Code. ↩
Спецификация COLRv1 как будто бы нужна для поддержки широкого спектра новых техник работы со шрифтами наподобие градиентов, палитр, встроенных глифов и тому подобных, но пока, похоже, в основном применяется для эмодзи. ↩
Дополнение: пара людей из Google неформально связалась со мной относительно этого бага. Доминик Рётчес отметил, что 🧺 — один из самых сложных для рендеринга глифов, но в моих тестах он вроде бы вообще не вызывал этого бага — на создание лэйаута ❤️ и 🤯 требуется 1600 мс, но 🧺 и 🫠 занимают 0,2 мс, из чего можно сделать вывод, что это проблема конкретного глифа.
Это согласуется с замечанием Тома Роджеро о том, что шрифт можно пропатчить для повышения производительности. Если я узнаю, что его пропатчили, то выпущу дополнение к статье; это будет явно быстрее, чем ждать распространения новой версии Safari! ↩

