Skip to content

Commit 465da84

Browse files
author
Andrew Schamp
committed
Add web-vitals measuring and event tracking
1 parent c84b7de commit 465da84

File tree

6 files changed

+78
-2
lines changed

6 files changed

+78
-2
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@
7777
"styled-components": "^5.0.1",
7878
"tar": "^6.0.1",
7979
"topojson-client": "^3.1.0",
80-
"uuid": "^8.3.0"
80+
"uuid": "^8.3.0",
81+
"web-vitals": "^1.1.0"
8182
},
8283
"scripts": {
8384
"start": "cross-env TSC_COMPILE_ON_ERROR=false react-scripts -r @cypress/instrument-cra start",

src/components/Analytics/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
trackCopyLink,
88
trackShare,
99
trackVoteClick,
10+
trackWebVitals,
1011
} from './utils';
1112

1213
export default PageviewTracker;
@@ -19,4 +20,5 @@ export {
1920
trackCopyLink,
2021
trackShare,
2122
trackVoteClick,
23+
trackWebVitals,
2224
};

src/components/Analytics/utils.ts

+46-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export enum EventCategory {
4444
EXPOSURE_NOTIFICATIONS = 'exposure notifications',
4545
SEARCH = 'search',
4646
VACCINATION = 'vaccination',
47+
WEB_VITALS = 'web vitals',
4748
NONE = 'none', // use NONE for development
4849
}
4950

@@ -65,6 +66,11 @@ export enum EventAction {
6566
REDIRECT = 'redirect',
6667
FOCUS = 'focus',
6768
NAVIGATE = 'navigate',
69+
CLS = 'Cumulative Layout Shift (*1000)',
70+
FCP = 'First Contentful Paint (ms)',
71+
FID = 'First Input Delay (ms)',
72+
LCP = 'Largest Contentful Paint (ms)',
73+
TTFB = 'Time to First Byte',
6874
}
6975

7076
/**
@@ -77,9 +83,17 @@ export function trackEvent(
7783
label?: string,
7884
value?: number,
7985
nonInteraction?: boolean,
86+
transport: 'beacon' | 'xhr' | 'image' = 'beacon',
8087
) {
8188
if (category !== EventCategory.NONE) {
82-
ReactGA.event({ category, action, label, value, nonInteraction });
89+
ReactGA.event({
90+
category,
91+
action,
92+
label,
93+
value,
94+
nonInteraction,
95+
transport,
96+
});
8397
}
8498
}
8599

@@ -106,3 +120,34 @@ export function trackShare(label: string) {
106120
export function trackVoteClick(label: string) {
107121
trackEvent(EventCategory.VOTE_2020, EventAction.CLICK_LINK, label);
108122
}
123+
124+
/**
125+
* Callback passed to web-vitals to report important performance events
126+
*/
127+
export function trackWebVitals({
128+
name,
129+
delta,
130+
id,
131+
}: {
132+
name: 'CLS' | 'FID' | 'LCP' | 'FCP' | 'TTFB';
133+
delta: number;
134+
id: string;
135+
}) {
136+
trackEvent(
137+
EventCategory.WEB_VITALS,
138+
EventAction[name],
139+
// The `id` value will be unique to the current page load. When sending
140+
// multiple values from the same page (e.g. for CLS), Google Analytics can
141+
// compute a total by grouping on this ID (note: requires `eventLabel` to
142+
// be a dimension in your report).
143+
id,
144+
// Google Analytics metrics must be integers, so the value is rounded.
145+
// For CLS the value is first multiplied by 1000 for greater precision
146+
// (note: increase the multiplier for greater precision if needed).
147+
Math.round(name === 'CLS' ? delta * 1000 : delta),
148+
// Use a non-interaction event to avoid affecting bounce rate.
149+
true,
150+
// Use `sendBeacon()` if the browser supports it.
151+
'beacon',
152+
);
153+
}

src/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import ReactDOM from 'react-dom';
33
import './index.css';
44
import App from './App';
55
import * as serviceWorker from './serviceWorker';
6+
import reportWebVitals from './reportWebVitals';
7+
import { trackWebVitals } from './components/Analytics';
68
import * as Sentry from '@sentry/react';
79
import { initFullStory } from 'common/fullstory';
810

@@ -30,3 +32,5 @@ ReactDOM.render(<App />, document.getElementById('root'));
3032
// unregister() to register() below. Note this comes with some pitfalls.
3133
// Learn more about service workers: https://bit.ly/CRA-PWA
3234
serviceWorker.unregister();
35+
36+
reportWebVitals(trackWebVitals);

src/reportWebVitals.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// implementation comes from a later version of create-react-app that this app was created from,
2+
// but the implementation seems worth reusing.
3+
// copied from https://github.com/facebook/create-react-app/blob/4e97dc75ad0c859fde7e2ffdaf9d5bd7d107b21a/packages/cra-template-typescript/template/src/reportWebVitals.ts
4+
5+
import { ReportHandler } from 'web-vitals';
6+
7+
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
8+
if (onPerfEntry && onPerfEntry instanceof Function) {
9+
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
10+
getCLS(onPerfEntry);
11+
getFID(onPerfEntry);
12+
getFCP(onPerfEntry);
13+
getLCP(onPerfEntry);
14+
getTTFB(onPerfEntry);
15+
});
16+
}
17+
};
18+
19+
export default reportWebVitals;

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -20417,6 +20417,11 @@ web-namespaces@^1.0.0:
2041720417
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec"
2041820418
integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==
2041920419

20420+
web-vitals@^1.1.0:
20421+
version "1.1.0"
20422+
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-1.1.0.tgz#7f410d9a1f7a1cd5d952806b45776204b47dc274"
20423+
integrity sha512-1cx54eRxY/+M0KNKdNpNnuXAXG+vJEvwScV4DiV9rOYDguHoeDIzm09ghBohOPtkqPO5OtPC14FWkNva3SDisg==
20424+
2042020425
webidl-conversions@^4.0.2:
2042120426
version "4.0.2"
2042220427
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"

0 commit comments

Comments
 (0)