Галоўная / Артыкулы / Выконанасць фронтэнду: ад незаўважаных моментаў пад час перагляду коду да паказнікаў продукту

Выконанасць фронтэнду: ад незаўважаных моментаў пад час перагляду коду да паказнікаў продукту

Дазнаеце, чаму адзын статус пасэрання перагляду коду недасты, калі саме Core Web Vitals маюць значэнне, і як вимерваць та вылечыць проблемы з карэнтамом React у рэальных умовах.

2316 слоў

Код працав у рамках перагляду

Ваша заявка на падзею ў порядку. Логіка правильная. Усі тэсты працаваюць. Яны былі падтвердзены старшым інжынерам.

Вы выкладзеце змяны ў четвер увечары.

У пятніцу вярхам прыходзіць паведамленне ад менаджера продукту: «Люди кажу, што прыстанок работае повольна».

Тады вы запускаете Chrome DevTools. На вашам MacBook Pro, падключаным да домашней оптавай мережы, з Chrome без дзесятка расширэнняў, усё выглядае плавна.

Але вашы корыстувачы не маюць такой налады.

Большасць з іх викорыстоўвае пристанок середньяго класу на Android з калькі лет назад, які працуе через 4G-з’ўязак, які часта пераходзіць на 3G. Яны можа знаходзіцца ў Джакарте, Лагасе чы Баку — месцах, дзе сама затрэмтванасць меркавай можа дадаць 200–400 мілісекунд да кожнага запиту.

Ваш прыстанок змушае іх сядзець і чакаць.

Чаму існуе гэты разлік

Большая частка разработчыкаў фронтэнду працуюць за майже ідеальных умоў, а потым выкладзаюць своі расробкі ў значна болей хаотычныя супакоўкі. Самэ гэта неадаптаванне є прычыной паўстання проблем з аддачай.

Ёсць тое, што ваша ўздэнная среда разработкі ад вас скрывае:

Обмежэнне працы CPU. У вашай машына для разработкі є достатнька вычысловой моці. Інструменты Chrome DevTools можаюць сімуляваць споваленне CPU у 4 або 6 разоў, але малая колькасць людзей трытаеся ўвімкнуць гэта.

Умовы сеті. Тэставанне на localhost означае нуль затрымкі. Рэальныя корыстнікі сталкаюцца з часам перадачы даных ад 100 да 500 мілісекундаў. Запыт на отрыманне даных, які здаецца мгновенным на вашай машыне, можа замарозіць інтерфейс на цэлую секунду, калі ён будзе працаваць у рэальным часе.

Размах пакета. Дадзенне бібліятэкі пад час напісвання коду здаецца безкоштовным — немае відчутных затрат. Але ў рэальных умовах тая ж самая залежнасць можа дадаць ў ваш пачатковы пакет ўсё 80 КБ, якія корыстувальніку з 3G павінен завантажыць прычымо да таго, калі ўсё будзе атрыбутавана.

Час парсавання JavaScript. Падача пакета на прыстрой — толькі першы крок. Потым браузер павінен яго парсаваць і запускать. На слабшым апаратнам забезпечэнні парсаванне пакета розмерам 500 КБ можа зайняць ад 3 да 4 секундаў.

Усе гэта разам стварае рэальную разліку межу тым, як ваша прыладка здаецца вам, і тым, як яна здаецца людям, якія ёю фактычна выкарыстоўваюць — і гэтая разліка зазвычай застаецца непазначальной, пакуль скаргі не вынудзяць яе стаць відкрытай.

Чаму прыдатнасць — гэта рашэнне па продукцыі

Інжынеры фронтэнду частацай прыкладзяюць пытанні яго карыстоўнасці да катэгорыі „тэхнічных дакладнасцей“. Менеджеры продуктамі часта ўвесь час ігнаруюць яго, пакуль гэта не ператворыцца на надзвычайную ситуацыю.

Жадны з эых падходаў не ёсць адпаведным.

Пытанні карыстоўнасці фронтэнду трэба адным з галоўных пунктав у размовах пра продукт, кারана яна вплывае на рэзультаты, якія фірма на самай працоўвае.

Дохады. Amazon паведаміў, што кожныя дадатковыя 100 мілісэкунд затрымкі коштаюць іх прыблізна 1% у выручцы. Пры дохадах у мільярд долераў на дзень, гэта дае 10 мільйонаў долераў за кожныя 100 мс. Меньшыя компаніі бачаць меншыя абсалютныя цифры, але законамернасць застаёцца тая ж.

Утриманне кліентаў. Больш за палову візітараў на мобільных прыстроях — 53% — пакідаюць сторанку, якая завантажваецца больш чым за 3 секунды. Яны рэдка канфліктуюць; яны проста адхоюцца і больш не вяртаюцца.

SEO. З 2021 года Google урахоўвае показнік Core Web Vitals у сваём алгорытме ранжавання. Медленная праця веб-сайту прычыніць його падняття нижэй у сторонцы рынку, што значыць, што менш людзей зможа яго знайсці.

Доступнасць. Швальна праця таксама є пытаннем равенства. Людзі, які викорыстоўваюць старэйшыя прыстроі та медленнейшыя з’ўязкі, пераважна прывязаны да краін з розвіваючайся эканамікай та груп з нізкім доходам. Дапрынтак, які працюе медленна, фактычна не дозволяе частыні аудытарыі яго викорыстоўваць.

Калі вы сфокусаваеце размову на доходзе, утриманні аудытарыі, видавальнай візуабельнасці та доступнасці, прынцыпы карыстоўнасці перестаюць вяскавацца як неабязковыя і становяцца базовай патрабоўкай.

Показнікі, якія насправды маюць значэнне

Нельга выправіць тое, што не вимерваеце, і нельга вимерваць тое, што не запрацавілі. Самэльга тут і становіць неабходнасць спяльная тэрміналогія для аналізу карыстоўнасці.

Core Web Vitals адміністрацыі Google наразе ўтвараюць найнадзейныя крэты для ціх цыляў.

LCP — Largest Contentful Paint

Гэты показнік фіксуе час, які трэба, каб адобразылася большая з візуальна видных елементаў на сторанцы — фактычна, тады, калі корыстнік вядома, што сторанца „завантажылася“.

Хораша ціннасць: менейш 2,5 секунд. Трэба паспрабаваць падняць: ад 2,5 да 4 секунд. Паслабная ціннасць: больш 4 секунд.

Тыповыя прычыны: занадта великія, неоптымізаваныя зображэння, ресурсы, якія спакоююць адобразаванне, і медленныя адпаведзі з сервера.

INP — Interaction to Next Paint

Гэты показнік вимервае затрымку межы дзеяння корыстніка — клік, натыск або нажатые клавіша — і моменту візуальнага адпаведзення экрана. У 2024 году ён замяніў FID (First Input Delay) як стандартны показнік інтерактыўнасці.

Хораша якосць: менейш 200 мс. Трэбуе паслаблення: ад 200 да 500 мс. Паслабная якосць: больш 500 мс.

Тыповыя прычыны: адзінакавыя вычыслення, якіе выпалююцца на главнай вяселі, і сінхронная работа, якая блакуе адрасаванне сторанкі.

CLS — Кумуляatywnыя змены распаковкі

Гэты показнік адражае, насколькі контент неспадзевана перасуваецца пад час завантажэння сторанкі. Значэнні коляцуюцца ад 0 (жадных зменаў) да вышэй, пры чым значэнні больш 1 считаюцца серьёзнымі.

Хораша якосць: менейш 0,1. Трэбуе паслаблення: ад 0,1 да 0,25. Паслабная якосць: больш 0,25.

Тыповыя прычыны: зображэнняя, якім не заданы шырына і высота, контент, які дынамічна дадаўся пасля завантажэння, і веб-шрыфты, якія запускаюцца пазней.

Як мерыць: вашы інструменты

Lighthouse (пачніце звярху)

Ачыніце Chrome DevTools, перайдзіце на вкладку Lighthouse і запусціце аудыт для мобільнага профілю з увёмканым режымам обмежэння працы.

Lighthouse вяртае бал з 0 да 100 па крытарыях Канцэрватыванне ресурсоў, Доступнасць, SEO і Найкращыя практыкі. Тое, што робіць яго справжна корыстным, — гэта тое, што ён поясняе чаму вы отрымалі такі бал і паказвае, што трэба прайсці правільна першым.

# Or run it from the CLI for CI/CD integration
npm install -g lighthouse
lighthouse https://yourapp.com --output html --output-path report.html

Важна: ўсё час запускайце Lighthouse у вікні анонімнасці. Інсталаваныя дадаткі для прыгледчыка можу спаксаваць рэзультаты.

React DevTools Profiler

Гэты інструмент, можа, ўсё менш вжываны разработчыкамі React, незважаючы на тое, што ён адны з найбольш інформатыўных.

Ён выкорыстоўваецца так: ачыніце React DevTools, перайдзіце на вкладку Profiler, нажміце «Запісваць», ведзіце дзейнасці з вашым прыгледчыкам, а пасля — стаўце запісваць.

Рэзультатам являецца графік у вачынку, який паказвае кожную перрадзіцельню — якія компаненты былі запускаець, што іх спрыяла і сколькі часу кожная з іх займала.

What to look for:
- Components rendering more than they should
- Renders triggered by unrelated state changes
- Expensive components re-rendering on every keystroke

Бібліятэка Web Vitals

Якщо вы хочаце зафіксаваць, як ваша прыглутка работае для рэальных візітароў, а не толькі пад локальным сеансам DevTools, бібліятэка web-vitals — гэта самы спосаб:

import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(metric => {
  // Send to your analytics service
  console.log('LCP:', metric.value);
});onINP(metric => {
  console.log('INP:', metric.value);
});onCLS(metric => {
  console.log('CLS:', metric.value);
});

Гэты падход дае вам даны з рэальных сеансаў корыстнікаў, а не цифры, створаныя пад штучным лабараторным умовамі.

Перрадзіцельна, пра якую вы не ведалі

Адна з найхутчэйшых проблем з працэйнасцю React не паводзіцца сабой явнымі сімптамамі. Яна зазвычай выглядае так:

// ❌ Problem: selecting the full user object
function Header() {
  const user = useSelector(state => state.user);
  return <div>{user.name}</div>;
}

Этый компонент будзе пераранжавацца ў кожны момент, калі змянюецца будзь-яка атрыбутацыя ў state.user, незалежна ад таго, чытвае компонент насправдзе гэтыя атрыбуты. Якщо об’ект пользователя мае двадцать поль і пяць з іх часта змінююцца, Header пераранжаваецца у пяць разоў чырэзнацькі.

// ✅ Fix: select only what you need
function Header() {
  const name = useSelector(state => state.user.name);
  return <div>{name}</div>;
}

За дапамогою гэтай змены Header реагуе толькі на змяны ў name. Це змена на адну лінію, але ўплыв на працэздатнась рэальны.

Тая ж логіка прыменяецца і да Context:

// ❌ Problem: consuming the full context
function ThemeButton() {
  const { theme, user, notifications } = useAppContext();
  return <button className={theme}>Click</button>;
}
// ✅ Fix: split contexts by update frequency
const ThemeContext = createContext();
const UserContext = createContext();function ThemeButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Click</button>;
}

Lazy Loading: Паставіць канец адправкі коду, якогу корыстнікам не трэба

Частая памылка ў проектах на React — адправка цэлага пакета прыложэння ў момент першага завантажэння стороніцы — укладаючы ў гэты пакет код для маршрутаў, якія візітар не ачыніў і можа ніколі не ачыніць.

// ❌ Problem: all routes loaded upfront
import CheckoutPage from './pages/CheckoutPage';
import AdminDashboard from './pages/AdminDashboard';
import SettingsPage from './pages/SettingsPage';
function App() {
  return (
    <Routes>
      <Route path="/checkout" element={<CheckoutPage />} />
      <Route path="/admin" element={<AdminDashboard />} />
      <Route path="/settings" element={<SettingsPage />} />
    </Routes>
  );
}
// ✅ Fix: lazy load each route
import { lazy, Suspense } from 'react';
const CheckoutPage = lazy(() => import('./pages/CheckoutPage'));
const AdminDashboard = lazy(() => import('./pages/AdminDashboard'));
const SettingsPage = lazy(() => import('./pages/SettingsPage'));function App() {
  return (
    <Suspense fallback={<PageSkeleton />}>
      <Routes>
        <Route path="/checkout" element={<CheckoutPage />} />
        <Route path="/admin" element={<AdminDashboard />} />
        <Route path="/settings" element={<SettingsPage />} />
      </Routes>
    </Suspense>
  );
}

За такой наладкі кожны маршрут стае самастайным часткам, тады корыстнікі завантажаюць толькі код, які неабходны для сторанкі, яюя ў рэальнасці пераглядаюць. У большых прыкладах аплікацый такая змена сама па сабе можа зменіць пачатковы бандл на 40-60 процэнтав.

Калі НЕ трэба оптымізаваць: пастка useMemo

Большасць кансалтатываў па прыдазе працоаспрэтнасі прахоўвае гэты аспект: частая оптымізацыя можа негатыўна паўлінуць ваш код.

useMemo і useCallback маюць своія затраты — адзначэнне памяці і пораўнанне залежнасцей на кожным рэндары. Якшчо вы ўжываеце іх не там, гэта можа прынесці меншую працоаспрэтнась, чым раней.

// ❌ Unnecessary — this calculation is not expensive
function UserCard({ user }) {
  const displayName = useMemo(
    () => `${user.firstName} ${user.lastName}`,
    [user.firstName, user.lastName]
  );
  return <div>{displayName}</div>;
}
// ✅ Just compute it — string concatenation is instant
function UserCard({ user }) {
  const displayName = `${user.firstName} ${user.lastName}`;
  return <div>{displayName}</div>;
}
// ✅ useMemo IS worth it here — genuinely expensive calculation
function DataGrid({ rows, filters }) {
  const filteredRows = useMemo(
    () => rows.filter(row => matchesAllFilters(row, filters)),
    [rows, filters]
  );
  return <Table rows={filteredRows} />;
}

Кансэпт: спачатку аналізуйце, прычыму не чыніце нічога, оптымізавайце толькі пасля чаго, а потым зноў мерзіце, каб парадактавацца, чы ўсё выйшла. Не пакладайцеся на інтуіцыю.

Якщо прыстрой аналізу ніколі не пазначае як компонент прычыну спаду выкарыстоўвання ресурсаў, залейце яго без запісу — даданая складнасць не варта тых зусиль.

Оптымізацыя зображэнняў: простыя спосабы

Зображэння часта ў значныяй меры вплываюць на повольную загрузку стораніц, але ў той жытак яны таксама належаць да найпростыях проблем для рашэння.

// ❌ Unoptimized: full-size image, no lazy loading
<img src="/hero-image.png" />
// ✅ Optimized: modern format, explicit dimensions, lazy loading
<img
  src="/hero-image.webp"
  width={1200}
  height={600}
  loading="lazy"
  decoding="async"
  alt="Hero image"
/>

Калькі прычыннаеў даюць рэальны эфект:

Заменіце форматы PNG або JPEG на WebP або AVIF. WebP зазвычай зменшае размер файлу на 25–35 процэнтав у пораўнанні з JPEG пры адной і той жа якосці. AVIF дастаўнае ўщэ больш компресуе файлы, хоця падтрымка яго ў браузерах яшчэ не є такой шырокай.

Заўжды задавайце атрыбуты шыроў і высоты явна. Гэта не дазволіць браузеру змянюваць макет пасля загрузкі зображэння, што безпасебна падыйдзе вашаму рэйтингу CLS.

Застосавайте loading="lazy" да ўсему, што знаходзіцца пад відпаваемым роўнем. Тады браузер чакае з загрузкай таго зображэння пакуль корыстнік не пачне яго прасуваць.

Дадзіце decoding="async" для зображэнняў, якія не є критычнымі для першага адразу відобразэння. Це дазволяе браузеру декодаваць зображэнне паўтарным потокам, а не блакуець процес відобразэння.

Рэальныя компрэсіі

Жадная тэхніка паўшчыны не є безкоштовной. Ёсць чыстая характарыстыка таго, што вам даводзіцца паспяшыць:

Разбілення коду па маршруты зменшые пачатковы размер бандлу, але спрыяе невялікам затрымленню праз першы раз, калі корыстувач пераходзіць на новы маршрут. Актыўнае заваносць зображэнняя прышвыджае пачатковы заванос, але змушвае зображэнняя праказна з’являцца праз прасуванне сторонкі. Абгортаванне дарогіх вычысленняў у useMemo зменшые колькість павтаральных перрэндероў, але цэна — дадатковая складнасць коду і паслабленая чытаемасць. Кэшаванне через Service Worker робіць паўтарныя візіты практычна мгновеннымі, але стварае сложную логіку анулювання кэшу. Рэндарыўанне з боку сервера або статычнае генераванне даюць шырокі час адображэння сторонкі і лепшы SEO, але для гэтага трэба больш інфраструктуры сервера і дадатковая складнасць практычнага запуску коду.

Асновны прынцып усьго гэтага: ніколі не оптымізавайце пад канкрэтны показнік, які вы насправдзе не змерылі.

Рэйтынг Lighthouse у розмяре 95 нічога не сказвае пра тое, чы рэальныя корыстувачы маюць хорашы досвід. Адаптавайте свой прыкладнік за дапамою бібліятэкі Web Vitals, збірайце даныя ад практычных візітараў, выклікайце справжню прычыну затрымкі, вылечыце самэ гэта пытанне, а потым зноў пераканайцеся ў эфектывасці рашэння.

Практычны чарткі

Перад тым, як запусціць новую функцыю, перагляньце гэты чарткі:

Performance Checklist
─────────────────────
□ Run Lighthouse on mobile with throttling (target score: 90+)
□ Check bundle size with webpack-bundle-analyzer or source-map-explorer
□ Verify all routes are lazy loaded
□ Confirm images use WebP/AVIF with explicit dimensions
□ Profile with React DevTools — no unnecessary re-renders
□ Check Core Web Vitals in production with web-vitals library
□ Test on a real mobile device, not just DevTools emulation
# Install bundle analyzer
npm install --save-dev webpack-bundle-analyzer
# Or for Vite
npm install --save-dev rollup-plugin-visualize

Вывык

Работа над аддачай прыдатнасці — гэта не тымчасовы захад, які вы адрабатваеце перед запускам, і не дадатковы элемент, які прыўязваеце пасля таго, калі функцыя вже працюе. Гэта практыка — сукупнасць прыzwычак і інструментаў, якія ўжо ўтворыліся ў вашай ўседзённей робочай схеме.

Інжынеры, які ствараюць найшырэйшыя прыкладнікі, не ўжо з самага пачатку адзінакова талантаваныя за тых, хто стварае медленныя. Яны проста адмацоўваюць рэзультаты болей стабільна. Яны ведаюць, який інструмент падходзіць для кожной проблемы, і усваялі простую схему: спачатку адмацоўваць, пасля вылечыць толькі тое, на што вказуюць даны, а потам зноў адмацоўваць, каб парадкавацца, чы то дапамогла.

Прывітны прыкладнік можа перайсці чераз усе перагляды коду, але ўсё рава заставіць рэальных корыстувачоў разчараванымі.

Адмацоўваць. Аналізаваць. Вылечыць тое, што действительна мае значэнне.

Спадзяючыся на чытанне

  • Скорачэнне розміру React Prop-Drilling і God Components — Дзеясавайце сэм конкрэтных шаблонаў рефакторавання для разбівання вялікіх компонентаў React пасля ізоляцыі стану, запошуку дадзеных, праваў і логікі завантажэння, а не проста па разбівцы файлоў.
  • Как узбегаць ад багоў мовчазнага стану, вызваных мутацыямі каштоў у JavaScript — Дазвольце дазнацца, чаму мутацыі об’ектаў і масэвых структураў праз каштоў нарабляюць проблемы з перзапісам дадзенняў у React, чаму функцыя spread копіюе толькі паверхневыя элементы, і як безпечна ствараць глэбокі клон стану.
  • Як развіваўся фронтэнд-інжыніринг: ад стылювання да систем з можласцю масштабавання — Рассказвае пра пераход з базовага HTML/CSS/JS да архітектуры компанентав, кэшавання, монорепоў і методаў аналізу, неабходных для надзеяння мільйонамў корыстувачаў.