Frontender's notes [ru]
Ведущий канал о современном фронтенде: статьи, новости, практики, вайбкодинг и автоматизация фронта ИИ-агентами. Личный блог автора - @just_genych По вопросам рекламы или разработки - @g_abashkin
نمایش بیشتر📈 تحلیل کانال تلگرام Frontender's notes [ru]
کانال Frontender's notes [ru] (@frontendnoteschannel_ru) در بخش زبانی روسی بازیگری فعال است. در حال حاضر جامعه شامل 31 991 مشترک است و جایگاه 4 125 را در دسته فناوری و برنامهها و رتبه 20 191 را در منطقه روسيا دارد.
📊 شاخصهای مخاطب و پویایی
از زمان ایجاد در невідомо، پروژه رشد سریعی داشته و 31 991 مشترک جذب کرده است.
بر اساس آخرین دادهها در تاریخ 20 ژوئیه, 2026، کانال فعالیت پایداری دارد. در ۳۰ روز گذشته تغییر اعضا برابر -409 و در ۲۴ ساعت گذشته برابر -19 بوده و همچنان دسترسی گستردهای حفظ شده است.
- وضعیت تأیید: تأیید نشده
- نرخ تعامل (ER): میانگین تعامل مخاطب 6.91% است و در ۲۴ ساعت نخست پس از انتشار، محتوا معمولاً 4.96% واکنش نسبت به کل مشترکان کسب میکند.
- دسترسی پستها: هر پست به طور میانگین 2 212 بازدید دریافت میکند. در اولین روز معمولاً 1 588 بازدید جمعآوری میشود.
- واکنشها و تعامل: مخاطبان بهطور فعال حمایت میکنند؛ میانگین واکنش به هر پست 5 است.
- علایق موضوعی: محتوا بر موضوعات کلیدی مانند браузер, api, css, интерфейс, загрузка تمرکز دارد.
📝 توضیح و سیاست محتوایی
نویسنده این فضا را محل بیان دیدگاههای شخصی توصیف میکند:
“Ведущий канал о современном фронтенде: статьи, новости, практики, вайбкодинг и автоматизация фронта ИИ-агентами.
Личный блог автора - @just_genych
По вопросам рекламы или разработки - @g_abashkin”
به لطف بهروزرسانیهای پرتکرار (آخرین داده در تاریخ 21 ژوئیه, 2026)، کانال همواره بهروز و دارای دسترسی بالاست. تحلیلها نشان میدهد مخاطبان بهطور فعال با محتوا تعامل دارند و آن را به نقطه اثرگذاری مهم در دسته فناوری و برنامهها تبدیل کردهاند.
در حال بارگیری داده...
| تاریخ | رشد مشترکین | اشارات | کانالها | |
| 21 ژوئیه | +3 | |||
| 20 ژوئیه | +2 | |||
| 19 ژوئیه | 0 | |||
| 18 ژوئیه | +2 | |||
| 17 ژوئیه | +4 | |||
| 16 ژوئیه | +3 | |||
| 15 ژوئیه | +2 | |||
| 14 ژوئیه | +2 | |||
| 13 ژوئیه | +1 | |||
| 12 ژوئیه | 0 | |||
| 11 ژوئیه | +1 | |||
| 10 ژوئیه | +2 | |||
| 09 ژوئیه | +2 | |||
| 08 ژوئیه | 0 | |||
| 07 ژوئیه | +2 | |||
| 06 ژوئیه | +2 | |||
| 05 ژوئیه | +2 | |||
| 04 ژوئیه | +3 | |||
| 03 ژوئیه | +4 | |||
| 02 ژوئیه | 0 | |||
| 01 ژوئیه | 0 |
| 2 | Как вытащить типы из Strapi-популейта на уровне компилятора
В Strapi есть засада: ответ от запроса меняет форму в зависимости от того, что ты в него сунул. Дергаешь статью без populate — получаешь только плоские поля. Кидаешь populate: { category: true } — категория уже внутри. А если еще и fields: ['name'] туда добавить, то категория придет урезанной. Итог: один эндпоинт выдает кучу разных вариантов, и статический TypeScript-интерфейс это никак не разрулит. Он либо нагло врет — обещает поля, которых в реальности нет, либо тупо сливается в any.
В статье показывают, как забить на эту проблему с помощью кодогенерации: вытаскиваешь схему из живого Strapi, генеришь по ней TypeScript-клиент, и самое мясо — заставляешь систему типов выводить ответ прямо из аргумента populate еще до компиляции. Типа как в Prisma: какой тип вернется, зависит от того, что ты передал в метод. Все на TypeScript, клиент open-source под MIT, ссылка внизу.
Почитать на Habr
👉 Frontender's notes | 1 683 |
| 3 | Как React-магазину не обрасти левыми зависимостями?
Вот классика: засунули карту банковской карты в entities/card/ui — туда попали маска номера, баланс и статус. Прошло два спринта, и в карточку влепили «заблокировать карту» и «изменить лимит». В итоге entity начала тащить features/block-card, features/change-card-limit, а ещё проверку прав мимо public API. Когда эту карточку воткнули в выбор счёта списания, с ней припёрлись лишние зависимости и проверки ролей. Никто не пострадал, но на фикс ушло время.
В entity оставили только чистый CardPreview, сценарии подняли уровнем выше, композицию дашборда переложили в widgets/card-summary, потом заново прогнали ролевые тесты. На ревью до названия папки дошли только в конце. Сначала обсуждали другое: какие будущие изменения эта граница позволит оставить без выноса за пределы.
В FSD 2.1 советуют начинать со страниц и вытаскивать код ниже, когда появляется переиспользование. Это норм дефолт, хотя, если рядом есть второй похожий потребитель, границу иногда можно резать раньше. Детали тут: https://habr.com/ru/articles/1060256/
👉 Frontender's notes | 1 750 |
| 4 | Гугл тебя кормит, гугл тебя поит. Даже целую аналитическую систему можно соорудить, не вынимая кошелька
Собираешь ответы через формы, вешаешь визуализацию на сайт — и, как только набирается нужная цифра, автоматом улетает письмо. Авторы в качестве демки обкатали это на исследовании туристических предпочтений. Жми, всё расписано.
Читать далее
👉 Frontender's notes | 1 759 |
| 5 | Типизация и детерминированная обработка состояния скролла при виртуализации списков с IntersectionObserver и кастомным Suspense в React 19
IntersectionObserver на бесконечных списках часто порождает гонки: элемент пересек границу, запрос ушел, а пользователь улетел скроллом дальше. Результат — повторные вызовы API и пустые мертвые зоны в рендере. В React 19 это решается через Suspense и хук use, делая обработку скролла детерминированной.
Замкнутое состояние скролла
Вместо разрозненных useState для startIndex и endIndex используем явный интерфейс ScrollState. Он обновляется атомарно — нельзя изменить один индекс без другого. Это исключает рассинхрон и гарантирует, что стейт всегда консистентен при передаче в Suspense-зависимый компонент.
interface ScrollState {
startIndex: number;
endIndex: number;
}
Синхронизация через Suspense и use
Когда IntersectionObserver срабатывает, мы сетим новый ScrollState. Компонент ItemsLoader внутри Suspense использует use(fetchItems(scrollState)) — это приостанавливает рендер до разрешения промиса. Наблюдатель пересоздается только в useEffect, который зависит от state.endIndex, то есть после того, как предыдущий диапазон гарантированно зафиксирован в DOM.
Типичная ошибка и trade-off
Ошибка: пытаться блокировать скролл флагами isFetching или отключать observer вручную. Это ломает UX и не решает проблему гонок, так как флаг может быть false до завершения ререндера. Минус подхода — он ломает классический поток «эффект -> стейт -> ререндер», требуя отказа от кастомных useEffect-решений. Но плюс — полная детерминированность: новые данные грузятся только после рендера старых.
Практический совет
Для production убедись, что fetchItems внутри use возвращает не просто массив, а структуру с totalCount или hasMore — это позволяет корректно остановить наблюдение при достижении конца списка. Иначе observer будет срабатывать на последнем sentinel-элементе бесконечно.
Вывод: Замыкание скролл-состояния в атомарный interface и ожидание рендера через Suspense устраняет гонки IntersectionObserver без костылей, делая поведение списка полностью предсказуемым для пользователя и инженера. | 1 633 |
| 6 | Pete Hunt снова в деле — теперь будет рулить Next.js
Гильермо Раух запостил в X: Vercel подтянул Пита Ханта и Ника Шрока (того самого, что придумал GraphQL). Пит — один из тех старых волков, что стояли у истоков React, теперь он возглавит Next.js. Ещё Vercel наконец-то вводит регулярный ежемесячный график security-релизов для Next.js — первый такой выйдет уже через пару дней.
→ Читать подробнее
Cloudflare переписал блог на EmDash
Cloudflare взял и перекроил свой блог целиком на EmDash — это их «духовный наследник WordPress» на Astro. Анонсировали они это, кстати, 1 апреля, но без шуток.
Linaria: создатель объясняет, зачем городил новое
«Runtime CSS-in-JS — всё, приплыли», — заявляет мейнтейнер Linaria. Теперь он наваял dx-styles, zero-runtime альтернативу, которая всё жует на этапе сборки. Гайд по миграции с Linaria уже есть.
ReactBench: кодинг-агентов проверяют на React-коде
Команда React Scan и Million.js запилила eval для coding-агентов на «реалистичной React-работе». Задача — отсеять модели, которые блистают в других бенчмарках, но тупят на нормальном React-коде. Пока лидирует GPT 5.6 Sol.
→ Подробнее
React Native Skia 2.9 — выкатили
Вышла свежая версия библиотеки для 2D-графики под React Native.
shadcn/typeset: полная типографика для HTML
Готовая типографская система для обработанного HTML (ну, там вывод Markdown) — один CSS-файл и один класс-обёртка. Настраивается парой CSS-переменных. Есть конструктор, если хочешь заморочиться.
И ещё:
— EmDash — опенсорс от Cloudflare
— shadcn/typeset на GitHub
— Preact 11.0 Beta 2 — «одна из последних бет»
— react-dropzone 17.0
— React Mosaic 7.0
— TanStack: график популярности npm-пакетов
👉 Frontender's notes | 1 519 |
| 7 | Ищем новичков во frontend-разработке и вёрстке сайтов.
Айтилогия запускает бесплатное обучение, где будет:
1. Практика на реальном заказе ценой до 10 000₽.
2. Разбор работ куратором.
3. Задачи от Fullstack-разработчика с 12-летним опытом.
4. Именной сертификат.
И главное — ты почувствуешь уверенность.
Потому что увидишь, что выполнить заказ на frontend-проект тебе по силам.
👉 Приходи на бесплатное обучение и зови с собой друзей
🔥 С 2019 Айтилогия стабильно помогает с обучением, практикой, зарабатывать на фрилансе и проходить собеседования. | 1 394 |
| 8 | Как скрестить Symfony 8 с Angular 22?
Ровно с этого вопроса я и стартанул, когда полез разбираться во всю эту кашу. Но после того как разгреб, могу смело заявить: норм тема, рабочая, и поддерживать такую архитектуру без геморроя вполне реально.
В посте — детальное описание того, как замутить веб‑приложение с удобной обвязкой из Docker контейнеров. CI/CD тут даже не упоминается — всё чисто про дев-сторону процесса разработки.
Автор пилит это всё в PHPStorm 2026.1.4 от Jetbrains, но вообще IDE — до лампочки. Теоретически можно даже в блокноте наваять приличный код.
Читать далее
👉 Frontender's notes | 1 652 |
| 9 | Типизация и детерминированная обработка race condition при конкурентной записи в WebSocket через AbortController и AsyncQueue в React 19
Race condition в WebSocket-соединениях — не просто теоретическая проблема. В production она возникает, когда несколько компонентов одновременно пытаются отправить данные: два сабмита формы, два useEffect с разными контекстами. Сообщения перемешиваются, теряются, или соединение падает с нечитаемым traceback. Стандартный WebSocket не гарантирует порядок, если не управлять очередью вручную.
Почему это важно для React 19
React 19 не предоставляет магического решения для WebSocket. useCallback и AbortController — стандартные API, работающие с React 16, но в 19-й версии они стабильны, и связка с очередью чувствует себя уверенно. Однако race condition остаётся: два вызова send из разных компонентов без синхронизации приводят к хаосу.
Решение: AsyncQueue и AbortController
Берём самописный AsyncQueue с FIFO и AbortController для отмены устаревших задач. Очередь гарантирует, что следующее сообщение отправится только после получения ответа на предыдущее. AbortSignal позволяет скипать задачу, если компонент размонтировался или пользователь передумал. Вот типизированный пример:
type SendTask = {
data: unknown;
signal: AbortSignal;
};
class AsyncQueue {
private queue: SendTask[] = [];
private processing = false;
async enqueue(task: SendTask): Promise<void> {
this.queue.push(task);
if (!this.processing) {
this.processing = true;
while (this.queue.length > 0) {
const current = this.queue.shift()!;
if (current.signal.aborted) continue;
await this.sendToWS(current.data);
}
this.processing = false;
}
}
private async sendToWS(data: unknown): Promise<void> {
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
ws.send(JSON.stringify(data));
ws.once('message', () => {
clearTimeout(timeout);
resolve();
});
});
}
}
const useSafeWS = () => {
const queueRef = useRef(new AsyncQueue());
const send = useCallback((data: unknown, signal?: AbortSignal) => {
const controller = new AbortController();
const effectiveSignal = signal || controller.signal;
queueRef.current.enqueue({ data, signal: effectiveSignal });
if (!signal) controller.abort();
}, []);
return { send };
};
Типичная ошибка: игнорирование таймаутов и очистки
Главный подводный камень — если сокет завис на ожидании ответа, вся очередь блокируется. В production добавьте fallback на переподключение: при таймауте разрывайте соединение, очищайте очередь и инициируйте reconnect. Без этого компоненты будут ждать вечно, а состояние интерфейса застынет.
Trade-off: самописное vs библиотечное решение
Самописная очередь требует тестирования edge cases: таймауты, параллельные подключения, отмена задач. Библиотеки вроде ws-queue решают это из коробки, но добавляют зависимость и скрывают логику. Я выбираю своё — прозрачность дебага и ноль зависимостей. Минус: нужно покрыть тестами сценарии с зависанием и переподключением.
Вывод: Race condition в WebSocket решается через FIFO-очередь с AbortController, но не забывайте про таймауты и fallback на reconnect, иначе детерминизм превратится в блокировку. | 1 537 |
| 10 | Ты не шаришь в CSS: вопросы и ответы, апдейт 2026
Автор канала продолжает свою рубрику и наваял новый пост про CSS. До этого похожий текст про HTML залетел в топ и стал одним из самых популярных за последнее время.
Задача этого опроса — просто развлечь народ. Вопросы заточены под реальную работу, но сходу на них ответит не каждый. Не знаешь половину — пофиг, ничего страшного.
Почитать
👉 Frontender's notes | 1 818 |
| 11 | Фрактальный интерфейс для софта — звучит как бред, но есть нюанс
Кто-то предложил сделать интерфейсы в программах фрактальными. Идея в том, что такая штука подстраивается под мозги конкретного юзера и не лезет в его личное пространство. Приложение при этом работает как живой организм — само развивается вместе с пользователем, а не тупо стоит на месте.
Читать далее
👉 Frontender's notes | 1 958 |
| 12 | Детерминированный Suspense в React 19: Zero-CLS через типизированный fallback при SSR
Layout shift при SSR с Suspense — хроническая боль для CLS и LCP. Классический подход рендерит fallback только на клиенте, вызывая дёрганье UI во время гидрации. React 19 исправляет это через пропс clientOnlyFallback={false}, позволяя серверу вставлять типизированный fallback с фиксированной высотой.
Почему это важно
При SSR без детерминированного fallback браузер не резервирует место под асинхронный контент. В момент гидрации контент появляется, и CLS улетает в красную зону. Решение — серверный fallback с явно заданными размерами, который не меняется при гидрации.
Типизированный fallback с предсказуемой высотой
Используйте интерфейс с обязательными height (в пикселях) и placeholder, чтобы браузер зарезервировал место до загрузки данных:
interface FallbackProps {
height: number;
placeholder: string;
}
function StaticFallback({ height, placeholder }: FallbackProps) {
return (
<div
style={{
height: ${height}px,
opacity: 0.3,
background: '#f0f0f0',
pointerEvents: 'none'
}}
aria-hidden="true"
>
{placeholder}
</div>
);
}
function AsyncComponent() {
return (
<Suspense
fallback={<StaticFallback height={200} placeholder="Загрузка..." />}
clientOnlyFallback={false}
>
<ActualData />
</Suspense>
);
}
Типичная ошибка
Пропуск height или передача нерелевантной высоты. Если размер fallback'а не совпадёт с реальным контентом, CLS вернётся. Для динамических блоков без фиксированной высоты используйте aspect-ratio CSS-свойство в style или вычисляйте размер через IntersectionObserver на сервере.
Production-совет
Для карточек, списков и таблиц — резервируйте высоту с запасом 10% от ожидаемой. Для блоков с неизвестным контентом комбинируйте min-height и max-height, чтобы избежать пустоты или наложения. Тестируйте на реальных метриках Lighthouse.
Вывод: Типизированный fallback с clientOnlyFallback={false} — минимальный шаг к нулевому CLS в SSR, но требует точного знания размеров контента, иначе trade-off между стабильностью и производительностью. | 1 873 |
| 13 | npm 12, TypeScript 7 и Bun на Rust
Выкатили npm 12 — жизненные скрипты теперь по дефолту отрублены. Детали
TypeScript 7.0 — наконец-то финальный Go-релиз, который «в 10 раз шустрее». Но полного API пока нет, так что советуют сидеть на 6.0. Релиз
Чувак, создавший Bun, расказал, как перетащил рантайм с Zig на Rust — сделал это через Claude Code, потратив ~$165k по ценам API. Rust-версия станет базой для Bun 1.4, выкат ожидается со дня на день. История
👉 Frontender's notes | 2 081 |
| 14 | Виртуальные Scroll-контейнеры
Свежий выпуск про штуку под названием ng-virtual-scroll-view. Ранее уже разбирали универсальные виртуализированные списки, которые юзали одноосевой виртуальный контейнер для скролла. Теперь же из проекта ng-virtual-list вылупилась двухосевая версия — тот самый ng-virtual-scroll-view.
Эта штука дружит с Angular версий с 14 по 22. Глянуть API и примеры использования можно в документации.
👉 Frontender's notes | 2 364 |
| 15 | Кажется, digital снова меняется быстрее, чем мы успеваем это осознать.
На днях Amazon Ads встроил кнопку «Добавить в корзину» прямо в пульт от телевизора. Теперь купить товар можно буквально во время просмотра фильма или сериала — без поиска сайта и переходов. Похоже, борьба за внимание закончилась. Началась борьба за одну кнопку.
Такие изменения лучше всего показывают, куда движется рынок. И если работаешь в digital или IT, важно замечать не только громкие новости, но и понимать, какие возможности они открывают.
Поэтому мы собрали подборку людей, которые не просто следят за новостями, а первыми разбирают, что действительно изменит рынок.
Здесь можно почитать подборки, обзоры, реальные кейсы и идеи, которые помогают смотреть на digital и IT немного шире.
Сохранить подборку себе 📨 | 1 747 |
| 16 | Если хочешь развиваться в машинном обучении — решать реальные бизнес-задачи или уйти в исследования — в Центральном университете есть магистратура под оба сценария. И на нее можно получить грант до 75%. Места ограничены, дедлайн подачи заявок — 20 августа.
«Машинное обучение» — это направление с несколькими форматами и треками. В офлайн-формате (пары по вечерам и в выходные в центре Москвы) можно выбрать один из трех треков:
⚫️Индустриальный — сильная база в ML, современные инструменты, реальные задачи от партнеров
⚫️Научный (AIRI × ЦУ) — сложные модели, исследования, подготовка к аспирантуре и работе в передовых лабораториях
⚫️ML в электронной коммерции × Lamoda — работа с реальными данными Lamoda, применение ML для бизнес-задач и возможность попасть на стажировку в компанию
Для тех, кто хочет учиться из любой точки мира, есть онлайн-формат: основной трек и продвинутый — для специалистов с опытом в ML. Это полноценная альтернатива офлайну с теми же преподавателями и курсами.
Магистратура в Центральном университете — это 2 года обучения, которое можно совмещать с работой, и диплом государственного образца. Карьерная поддержка начинается еще во время учебы: консультации, тренировочные собеседования и помощь с трудоустройством. Студенты уже в процессе обучения выходят на новые позиции или повышаются в грейде в Яндексе, Авито, Т-Банке и других компаниях.
Поступление проходит через грантовый конкурс — это одновременно способ попасть на программу и возможность выиграть финансовую поддержку на все время обучения: грант покрывает до 75% стоимости.
В 2026 году доступно 550 грантов на все программы магистратуры. Подробнее о программах и условиях участия в конкурсе — по ссылкам
➡️Офлайн программа
➡️Онлайн программа | 1 |
| 17 | Типизация и детерминированное управление ресурсами через FinalizationRegistry и WeakRef в долгоживущих хуках с Suspense
React 19 с Suspense вводит состояние "засыпания" компонента, когда эффекты не запускаются и не очищаются. Это создает риск утечек WebSocket-соединений или Worker'ов, которые разработчики часто пытаются закрыть только через cleanup useEffect, не учитывая этот сценарий.
Проблема: useEffect не срабатывает при suspend
Когда компонент "засыпает", cleanup не вызывается, и ресурсы висят до размонтирования. Обычная практика с useRef удерживает ссылку, блокируя GC:
const wsRef = useRef<WebSocket>(null);
wsRef.current = new WebSocket('wss://...');
// cleanup не сработает при suspend
return () => wsRef.current?.close();
Ошибка: useRef удерживает сильную ссылку, не позволяя сборщику мусора освободить WebSocket даже при отсутствии эффекта.
Решение: типизированный менеджер ресурсов
Используем WeakRef (слабая ссылка) + FinalizationRegistry (обратный вызов при GC):
type ResourceCleanup = () => void;
function createResourceManager<T extends object>(
name: string,
cleanup: (resource: T) => void
) {
const registry = new FinalizationRegistry<ResourceCleanup>(
(cleanupFn) => cleanupFn()
);
return {
register(resource: T) {
const ref = new WeakRef(resource);
registry.register(resource, () => {
const res = ref.deref();
if (res) cleanup(res);
});
return ref;
},
unregister(resource: T) {
registry.unregister(resource);
}
};
}
Применение в хуке с Suspense:
const wsManager = createResourceManager('chat-ws', (ws) => ws.close());
function ChatComponent() {
const wsRef = useRef<WeakRef<WebSocket> | null>(null);
useEffect(() => {
const ws = new WebSocket('wss://chat');
wsRef.current = wsManager.register(ws);
return () => wsManager.unregister(ws);
}, []);
}
Смысл: WeakRef не блокирует GC, а FinalizationRegistry гарантирует cleanup после удаления объекта. Типизация через ManagedWeakRef<T>:
type ManagedWeakRef<T> = { ref: WeakRef<T>; unregister: () => void };
Важное предупреждение
FinalizationRegistry не гарантирует вызов — он приблизительный и может не сработать при интенсивной работе GC. Поэтому всегда комбинируй с явным unregister() в cleanup useEffect. Не злоупотребляй WeakRef в каждом хуке — это низкоуровневый инструмент для редких кейсов (долгоживущие стримы, кэши вне React).
Вывод: WeakRef и FinalizationRegistry — последний рубеж защиты от утечек при Suspense, но детерминизм достигается только комбинацией с явной очисткой в cleanup, а не надеждой на GC. | 1 970 |
| 18 | Redux никогда не работал в React
То есть, серьёзно: взять голый redux и просто заюзать его в реакте не выйдет. Тебе обязательно понадобится какая-то «react-redux» обёртка. И тогда возникает логичный вопрос: на кой хрен тогда петь мантры про «Redux works with any UI layer»?
Читать далее
👉 Frontender's notes | 2 242 |
| 19 | О, понеслась. Во все тяжкие, стартанули 🤣
👉 Frontender's notes | 2 578 |
| 20 | Типизация и детерминированное разрешение конфликтов при кэшировании React Server Actions: staleTimes, revalidate и кастомные GC-стратегии
Когда видишь staleTimes и revalidate в React Server Actions, первое желание — поставить числа побольше и забыть. Но на production это заканчивается сессионными багами, которые не воспроизводятся локально. Часто разработчики воспринимают эти настройки как магию, а потом ловят stale data, которую не могут объяснить.
Типизация как контракт кэша
Делай дискриминационное объединение для каждого кеш-слота:
type CacheEntry<T> =
| { status: 'fresh'; data: T; timestamp: number }
| { status: 'stale'; data: T; lastRevalidate: number }
| { status: 'invalid' };
Это заставит компилятор проверить статус перед использованием данных. Никаких runtime-сюрпризов с невалидными записями. Типизируй staleTimes как контракт, а не таймер:
type StaleConfig = {
maxAge: number; // время свежести данных
staleWhileRevalidate: number; // окно показа старых с перезапросом
gcAfter?: number; // принудительная чистка
};
Кастомная GC-стратегия для долгоживущих данных
Для профиля пользователя: maxAge — 10 минут, staleWhileRevalidate — 1 час, gcAfter — 2 часа. Полный sweep убивает производительность. Используй вероятностное удаление:
function gcSweep(cache: Map<string, CacheEntry<unknown>>, config: StaleConfig) {
const now = Date.now();
for (const [key, entry] of cache) {
if (entry.status === 'invalid' ||
(entry.timestamp + config.gcAfter! < now && Math.random() < 0.1)) {
cache.delete(key);
}
}
}
Шанс 10% на удаление просроченной записи не даёт GC-взрыва и держит кеш в чистоте без блокировок.
Детерминированное разрешение конфликтов
Два способа. Первый — deduped fetch с типизированным локом:
const lock = new Map<string, Promise<unknown>>();
async function dedupedFetch<T>(key: string, fetcher: () => Promise<T>): Promise<T> {
if (!lock.has(key)) {
lock.set(key, fetcher().finally(() => lock.delete(key)));
}
return lock.get(key)!;
}
Второй — версионность: храни счётчик версий в кеш-слоте, при записи проверяй, что версия не устарела. Это решает гонки при параллельных revalidate.
Типичная ошибка: не типизировать состояние кеша и полагаться на runtime-проверки. В production это ведёт к race conditions, которые не видны в dev-режиме с одним пользователем. Строй явный жизненный цикл: через revalidate с проверкой типов решай актуальность данных, а GC делай фоновым и вероятностным.
Вывод: Явная типизация состояний кэша, декларативный staleTimes-контракт и вероятностный GC — это не про микрооптимизацию, а про детерминированное управление потоком данных в production-grade приложениях. | 2 119 |
