Последняя часть серии — про
Где Blob хранит данные? Из документации Chromium по blob storage system: данные
Есть обратная сторона: пока
Браузер внутри себя создаёт маппинг
Blob.Blob (Binary Large Object) — это иммутабельный объект с сырыми данными и MIME-типом. В отличие от ArrayBuffer, он не даёт прямого доступа к байтам — только асинхронно, через конвертацию:
const blob = new Blob(['<h1>Hi</h1>'], { type: 'text/html' });
const text = await blob.text();
const buf = await blob.arrayBuffer();
File, который возвращает <input type="file">, наследуется от Blob.Где Blob хранит данные? Из документации Chromium по blob storage system: данные
Blob создаются в процессе рендерера, где JS-код исполняется. Затем они передаются в процесс браузера. Подробнее про процессы можно почитать в моей статье. Если в памяти достаточно квоты — данные хранятся в RAM. Если квота заканчивается или Blob слишком большой, то браузер переносит данные на диск. Вот почему чтение из Blob асинхронное — браузер может возвращать данные с диска. Это также объясняет, почему он иммутабельный: если данные на диске, мутабельность потребовала бы файловой синхронизации, что сильно усложнило бы реализацию.blob.slice(start, end) не копирует данные, а создаёт новый Blob, который является view на часть существующего. Под капотом хранится ссылка на оригинальный Blob плюс смещение и длина.
const huge = new Blob([bigData]); // 100 МБ
const chunk = huge.slice(0, 1024); // view на первые 1 КБ, без копирования
Есть обратная сторона: пока
chunk жив, huge (и все его 100 МБ) не могут быть освобождены. Похожая ситуация с памятью может возникнуть и при создании URL на Blob.
const url = URL.createObjectURL(blob);
// url → "blob:https://example.com/550e8400-e29b-..."
img.src = url;
Браузер внутри себя создаёт маппинг
URL → Blob и удерживает Blob в памяти. Этот маппинг живёт, пока вы не вызовете URL.revokeObjectURL(url) или пока не закроется документ.👍16❤2💅2👌1🕊1
Тема того, как именно V8 понимает, что код стал «достаточно горячим» для оптимизации, зацепила меня почти сразу, как я начала копаться во внутренностях движка.
На докладах на это обычно не хватает времени — слишком много нюансов и хочется рассказать много всего и сразу. Поэтому я вынесла эту тему в отдельную статью, где подробно разобрала, как и когда происходят те самые заветные переходы между компиляторами внутри V8.
Горячий код в V8: что это значит?
На докладах на это обычно не хватает времени — слишком много нюансов и хочется рассказать много всего и сразу. Поэтому я вынесла эту тему в отдельную статью, где подробно разобрала, как и когда происходят те самые заветные переходы между компиляторами внутри V8.
Горячий код в V8: что это значит?
🔥22💅8❤3👍2💯1
Почему JSON.parse() может быть быстрее объектного литерала
Казалось бы,
Причин несколько. Во-первых, грамматика JSON тривиальна по сравнению с JS — у движка для неё отдельный, более простой и быстрый парсер. Объектный литерал — это полноценный JS-код, который проходит весь путь: токенизация → AST → байткод. Так же, как описано в блоге V8, большие объектные литералы могут парситься дважды — сначала при preparsing, потом при lazy-parsing. Строка внутри JSON.parse этой проблемы лишена.
В реальном кейсе с SSR-приложением на Redux такая замена дала улучшение Lighthouse-скора с 87 до 95 и снижение TTI на 0.7 секунды. Оптимизация актуальна и в 2026 году — фундаментальные причины никуда не делись, а V8 продолжает активно инвестировать в производительность JSON (например, недавний двукратный прирост JSON.stringify в V8 v13.8).
Казалось бы,
const config = {a: 1, b: 2, c: 3} — это самый прямой способ создать объект. Но если объект достаточно большой (от ~10 KB), JSON.parse('{"a":1,"b":2,"c":3}') окажется быстрее. На бенчмарке от GoogleChromeLabs на файле в 7 МБ JSON.parse оказался в 1.7× быстрее объектного литерала в V8, в Safari разница доходила до 2×.Причин несколько. Во-первых, грамматика JSON тривиальна по сравнению с JS — у движка для неё отдельный, более простой и быстрый парсер. Объектный литерал — это полноценный JS-код, который проходит весь путь: токенизация → AST → байткод. Так же, как описано в блоге V8, большие объектные литералы могут парситься дважды — сначала при preparsing, потом при lazy-parsing. Строка внутри JSON.parse этой проблемы лишена.
В реальном кейсе с SSR-приложением на Redux такая замена дала улучшение Lighthouse-скора с 87 до 95 и снижение TTI на 0.7 секунды. Оптимизация актуальна и в 2026 году — фундаментальные причины никуда не делись, а V8 продолжает активно инвестировать в производительность JSON (например, недавний двукратный прирост JSON.stringify в V8 v13.8).
🤯30🔥9❤3👍2💅1
Что такое back/forward cache (bfcache)
Когда пользователь нажимает «назад» или «вперёд», браузер может не загружать страницу заново, а достать из памяти полный снимок: DOM, JS-кучу, состояние скролла — всё. Страница замораживается при уходе и размораживается при возврате. Переход ощущается мгновенно, потому что это не навигация в привычном смысле — это restore.
Браузер делает эту оптимизацию автоматически, но страница должна соответствовать определённым условиям. Она не попадёт в bfcache, если:
- есть listener на
- открыт WebSocket
- на документе стоит
- есть незавершённая `IndexedDB`транзакция
- и другие причины
Полный список можно посмотреть на MDN.
Диагностировать всё это можно прямо в DevTools: вкладка Application → Back/forward cache. Там можно протестировать, попадает ли страница в bfcache, и если нет — увидеть конкретный список блокеров.
Для продакшена есть программный способ — PerformanceNavigationTiming.notRestoredReasons. Через него можно собирать данные об использовании bfcache в RUM-метриках и понимать, что ломает кэш у реальных пользователей.
По итогу bfcache — один из самых «дешёвых» способов ускорить воспринимаемую производительность. Ничего не нужно дополнительно оптимизировать — достаточно не ломать нативное поведение браузера.
Когда пользователь нажимает «назад» или «вперёд», браузер может не загружать страницу заново, а достать из памяти полный снимок: DOM, JS-кучу, состояние скролла — всё. Страница замораживается при уходе и размораживается при возврате. Переход ощущается мгновенно, потому что это не навигация в привычном смысле — это restore.
Браузер делает эту оптимизацию автоматически, но страница должна соответствовать определённым условиям. Она не попадёт в bfcache, если:
- есть listener на
unload- открыт WebSocket
- на документе стоит
Cache-Control: no-store- есть незавершённая `IndexedDB`транзакция
- и другие причины
Полный список можно посмотреть на MDN.
Диагностировать всё это можно прямо в DevTools: вкладка Application → Back/forward cache. Там можно протестировать, попадает ли страница в bfcache, и если нет — увидеть конкретный список блокеров.
Для продакшена есть программный способ — PerformanceNavigationTiming.notRestoredReasons. Через него можно собирать данные об использовании bfcache в RUM-метриках и понимать, что ломает кэш у реальных пользователей.
По итогу bfcache — один из самых «дешёвых» способов ускорить воспринимаемую производительность. Ничего не нужно дополнительно оптимизировать — достаточно не ломать нативное поведение браузера.
🔥38❤4👍4👏1💅1
Продолжая тему с небольшими оптимизациями в браузере — сегодня поговорим про заголовок
Полностью он используется так:
Что здесь происходит:
- 0–600 сек — ресурс свежий, отдаётся из кэша
- 600–630 сек — ресурс устарел, но браузер всё равно отдаёт его мгновенно, а в фоне идёт за новой версией
- после 630 сек — кэш полностью стух, пользователь ждёт
Ключевой момент: пользователь, попавший в stale-окно, видит старую версию. Фоновый запрос незаметно обновляет кэш, и уже в следующий раз посетитель получит свежие данные.
Где его используем, а где нет?
- Нехешированная статика (`/logo.png`, `/fonts/custom.woff2`) — используем, потому что URL не меняется при обновлении файла.
- API-ответы и HTML, которые меняются нечасто (каталог, лендинг, результаты поиска) — тоже используем, это даёт мгновенный ответ, а данные отстают максимум на пару минут.
- Хешированная статика (`main.a3f8c2.js`) — не имеет смысла использовать
- Критичные данные (баланс, цена, статус заказа) — тут уже нужен
И да, паттерн SWR знаком многим по React-библиотекам (swr, React Query), но лично я раньше не задумывалась о том, что это буквально тот же принцип, только взятый из HTTP.
stale-while-revalidate.Полностью он используется так:
Cache-Control: max-age=600, stale-while-revalidate=30Что здесь происходит:
- 0–600 сек — ресурс свежий, отдаётся из кэша
- 600–630 сек — ресурс устарел, но браузер всё равно отдаёт его мгновенно, а в фоне идёт за новой версией
- после 630 сек — кэш полностью стух, пользователь ждёт
Ключевой момент: пользователь, попавший в stale-окно, видит старую версию. Фоновый запрос незаметно обновляет кэш, и уже в следующий раз посетитель получит свежие данные.
Где его используем, а где нет?
- Нехешированная статика (`/logo.png`, `/fonts/custom.woff2`) — используем, потому что URL не меняется при обновлении файла.
- API-ответы и HTML, которые меняются нечасто (каталог, лендинг, результаты поиска) — тоже используем, это даёт мгновенный ответ, а данные отстают максимум на пару минут.
- Хешированная статика (`main.a3f8c2.js`) — не имеет смысла использовать
stale-while-revalidate, тут правильнее будет immutable, потому что файл по этому URL с хэшом не изменится никогда.- Критичные данные (баланс, цена, статус заказа) — тут уже нужен
no-cache.И да, паттерн SWR знаком многим по React-библиотекам (swr, React Query), но лично я раньше не задумывалась о том, что это буквально тот же принцип, только взятый из HTTP.
❤22👀1💅1
Недавно на рабочем проекте я обновляла Next.js с 12-й на 16-ю версию. Далось непросто, так как были кастомный сервер на NestJS и связка через пакет nest-next, который последний раз обновлялся три года назад. Больно и неприкольно, но мы справились 💪
Этот опыт вдохновил меня наконец заняться тем, что я так долго откладывала — сходить в отпуск. Ну и написать цикл статей про Next.js)
Так что впереди нас ждёт трёхнедельный перерыв на канале, а после него — новый цикл, не переключайтесь!
Этот опыт вдохновил меня наконец заняться тем, что я так долго откладывала — сходить в отпуск. Ну и написать цикл статей про Next.js)
Так что впереди нас ждёт трёхнедельный перерыв на канале, а после него — новый цикл, не переключайтесь!
1❤50🔥16👍6🙏1💯1💅1
А вот и обещанный новый цикл про Next.js под капотом.
И в первой части разберём, из каких слоёв состоит Next.js как система, как они связаны друг с другом и почему архитектура стала именно такой.
Next.js изнутри. Часть 1. Архитектура Next.js.
И в первой части разберём, из каких слоёв состоит Next.js как система, как они связаны друг с другом и почему архитектура стала именно такой.
Next.js изнутри. Часть 1. Архитектура Next.js.
1❤32🔥8💯2😐1💅1🤪1💊1
Продолжаем погружаться в Next.js. Сегодня разбираем Pages Router: что создаёт
Next.js изнутри. Часть 2. Как работает Pages Router.
next build, как сервер рендерит HTML при первом заходе и что происходит при клиентской навигации.Next.js изнутри. Часть 2. Как работает Pages Router.
2❤18👍4💅4
На очереди App Router, и материал получается такой плотный, что я решила разбить его на две части. В первой посмотрим на ключевые концепции и разберём момент первого рендеринга, без клиентской навигации и server actions.
Next.js изнутри. Часть 3. App Router: от запроса до гидратации.
Next.js изнутри. Часть 3. App Router: от запроса до гидратации.
1🕊9❤5💅5👍2🔥2
В продолжении разговора про App Router обсудим prefetch, Server Actions, клиентскую навигацию и даже немного затронем тему безопасности.
Next.js изнутри. Часть 4. App Router: навигация, кеш и мутации.
Next.js изнутри. Часть 4. App Router: навигация, кеш и мутации.
1❤7🤣3💅3👍2✍1
Не останавливаем своё погружение в Next.js! Сегодня поговорим про серверный слой и то, как вообще запрос доходит до рендера.
Next.js изнутри. Часть 5. Серверный слой.
Next.js изнутри. Часть 5. Серверный слой.
1👍12🔥3
А вот и моя самая любимая (или нет)) вещь в приложениях на Next.js — кастомный сервер! Посмотрим, что это такое, как он настраивается, и я даже поделюсь историей из своего рабочего проекта.
Next.js изнутри. Часть 6. Кастомный сервер.
Next.js изнутри. Часть 6. Кастомный сервер.
1🙏6❤4💅2
Потихоньку подходим к завершению цикла про Next.js (осталось немного!), и теперь на очереди сборка: Webpack, Turbopack, SWC и не только — что это такое, как оно всё работает и с чем его едят.
Next.js изнутри. Часть 7. Сборка.
Next.js изнутри. Часть 7. Сборка.
1❤17💅2👨💻1
Отдельно поговорим про dev-mode в Next.js и особенности его работы.
Next.js изнутри. Часть 8. Dev-mode.
Next.js изнутри. Часть 8. Dev-mode.
1❤🔥9💅1
На прошлой неделе я читала студентам лекцию про настройку инфраструктуры и рассказывала в том числе про кэширование слоёв при сборке Docker-образа.
Как всегда, стало интересно, как это работает под капотом?
Сборкой занимается BuildKit — компонент Docker, который исполняет Dockerfile. Он преобразует инструкции в граф операций и для каждой вычисляет ключ кэша. Ключи образуют цепочку: ключ текущей операции считается из её описания и ключа предыдущей. Если это
Если подходящий ключ уже есть в кэше, операция не выполняется, а BuildKit использует сохранённый результат. Если ключ изменился, операция выполняется заново. Следующие операции тоже получают другие ключи, поскольку зависят уже от нового результата.
Сам результат хранится как снимок файловой системы после операции. Локально снимки и служебные записи находятся во внутреннем хранилище конкретного builder-а: например, в Docker Desktop это хранилище внутри его Linux VM.
В CI всё зависит от runner-а. На постоянном runner-е локальный кэш может пережить сборку, а на одноразовом исчезнет вместе с машиной. Поэтому в CI кэш обычно явно экспортируют в registry или хранилище CI через
Именно поэтому в Dockerfile для Node.js сначала копируют
Ну а последняя часть цикла про Next.js будет на следующей неделе)
Как всегда, стало интересно, как это работает под капотом?
Сборкой занимается BuildKit — компонент Docker, который исполняет Dockerfile. Он преобразует инструкции в граф операций и для каждой вычисляет ключ кэша. Ключи образуют цепочку: ключ текущей операции считается из её описания и ключа предыдущей. Если это
COPY, к ним добавляется контрольная сумма копируемых файлов. Например, изменение базового образа меняет ключ FROM, вслед за ним — ключ следующего RUN и так далее.Если подходящий ключ уже есть в кэше, операция не выполняется, а BuildKit использует сохранённый результат. Если ключ изменился, операция выполняется заново. Следующие операции тоже получают другие ключи, поскольку зависят уже от нового результата.
Сам результат хранится как снимок файловой системы после операции. Локально снимки и служебные записи находятся во внутреннем хранилище конкретного builder-а: например, в Docker Desktop это хранилище внутри его Linux VM.
В CI всё зависит от runner-а. На постоянном runner-е локальный кэш может пережить сборку, а на одноразовом исчезнет вместе с машиной. Поэтому в CI кэш обычно явно экспортируют в registry или хранилище CI через
--cache-to, а перед следующей сборкой импортируют через --cache-from.Именно поэтому в Dockerfile для Node.js сначала копируют
package.json и lock-файл, затем выполняют npm ci и только после этого копируют исходники. Изменение кода в таком случае не инвалидирует слой с зависимостями. То есть порядок инструкций в Dockerfile определяет не только результат сборки, но и то, какую её часть придётся повторить при следующем запуске.Ну а последняя часть цикла про Next.js будет на следующей неделе)
👍25💅3❤2
Заключительная часть цикла про Next.js наконец-то здесь! Обобщим всё, о чём говорили до этого, и посмотрим на оптимизации.
Next.js изнутри. Часть 9. Способы оптимизации.
Next.js изнутри. Часть 9. Способы оптимизации.
1❤13💅1