or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

additional-web-vitals.mdattribution.mdcore-web-vitals.mdindex.mdthresholds.md

attribution.mddocs/

0

# Attribution Build

1

2

Enhanced measurement functions that include detailed attribution data for performance debugging and optimization. The attribution build provides the same API as the standard build but with additional diagnostic information to help identify the root cause of performance issues.

3

4

## Overview

5

6

The attribution build is slightly larger (~1.5K additional, brotli'd) but provides invaluable debugging information. Each metric callback receives a `MetricWithAttribution` object instead of a regular `Metric` object, containing an additional `attribution` property with diagnostic details.

7

8

## Import Pattern

9

10

```typescript

11

// Standard build

12

import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals";

13

14

// Attribution build - same function names, enhanced data

15

import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals/attribution";

16

```

17

18

## Capabilities

19

20

### CLS Attribution

21

22

Provides detailed information about layout shifts, including the specific elements that caused shifts and their impact.

23

24

```typescript { .api }

25

/**

26

* CLS measurement with attribution data for debugging layout shifts

27

* @param callback - Function to receive CLS metric with attribution data

28

* @param opts - Optional configuration including custom target generation

29

*/

30

function onCLS(callback: (metric: CLSMetricWithAttribution) => void, opts?: AttributionReportOpts): void;

31

32

interface CLSMetricWithAttribution extends CLSMetric {

33

attribution: CLSAttribution;

34

}

35

36

interface CLSAttribution {

37

/** Selector for the first element that shifted in the largest layout shift */

38

largestShiftTarget?: string;

39

/** Time when the single largest layout shift occurred */

40

largestShiftTime?: DOMHighResTimeStamp;

41

/** Layout shift score of the single largest shift */

42

largestShiftValue?: number;

43

/** The LayoutShift entry representing the largest shift */

44

largestShiftEntry?: LayoutShift;

45

/** First element source from the largestShiftEntry sources list */

46

largestShiftSource?: LayoutShiftAttribution;

47

/** Loading state when the largest layout shift occurred */

48

loadState?: LoadState;

49

}

50

```

51

52

**Usage Example:**

53

54

```typescript

55

import { onCLS } from "web-vitals/attribution";

56

57

onCLS((metric) => {

58

console.log('CLS score:', metric.value);

59

console.log('Worst shift element:', metric.attribution.largestShiftTarget);

60

console.log('Worst shift value:', metric.attribution.largestShiftValue);

61

console.log('Page state during shift:', metric.attribution.loadState);

62

63

// Send detailed data for analysis

64

analytics.track('cls_issue', {

65

value: metric.value,

66

element: metric.attribution.largestShiftTarget,

67

shiftValue: metric.attribution.largestShiftValue,

68

loadState: metric.attribution.loadState

69

});

70

});

71

```

72

73

### INP Attribution

74

75

Provides detailed information about the slowest interaction, including timing breakdown and responsible elements.

76

77

```typescript { .api }

78

/**

79

* INP measurement with attribution data for debugging slow interactions

80

* @param callback - Function to receive INP metric with attribution data

81

* @param opts - Optional configuration including duration threshold

82

*/

83

function onINP(callback: (metric: INPMetricWithAttribution) => void, opts?: INPAttributionReportOpts): void;

84

85

interface INPMetricWithAttribution extends INPMetric {

86

attribution: INPAttribution;

87

}

88

89

interface INPAttribution {

90

/** CSS selector of the element that received the interaction */

91

interactionTarget: string;

92

/** Time when the user first interacted */

93

interactionTime: DOMHighResTimeStamp;

94

/** Type of interaction ('pointer' | 'keyboard') */

95

interactionType: 'pointer' | 'keyboard';

96

/** Best-guess timestamp of the next paint after interaction */

97

nextPaintTime: DOMHighResTimeStamp;

98

/** Event timing entries processed within the same animation frame */

99

processedEventEntries: PerformanceEventTiming[];

100

/** Time from interaction start to processing start */

101

inputDelay: number;

102

/** Time spent processing the interaction */

103

processingDuration: number;

104

/** Time from processing end to next paint */

105

presentationDelay: number;

106

/** Loading state when interaction occurred */

107

loadState: LoadState;

108

/** Long animation frame entries intersecting the interaction */

109

longAnimationFrameEntries: PerformanceLongAnimationFrameTiming[];

110

/** Summary of the longest script intersecting the INP duration */

111

longestScript?: INPLongestScriptSummary;

112

/** Total duration of Long Animation Frame scripts intersecting INP */

113

totalScriptDuration?: number;

114

/** Total style and layout duration from Long Animation Frames */

115

totalStyleAndLayoutDuration?: number;

116

/** Off main-thread presentation delay */

117

totalPaintDuration?: number;

118

/** Total unattributed time not included in other totals */

119

totalUnattributedDuration?: number;

120

}

121

122

interface INPLongestScriptSummary {

123

/** The longest Long Animation Frame script entry intersecting the INP interaction */

124

entry: PerformanceScriptTiming;

125

/** The INP subpart where the longest script ran */

126

subpart: 'input-delay' | 'processing-duration' | 'presentation-delay';

127

/** The amount of time the longest script intersected the INP duration */

128

intersectingDuration: number;

129

}

130

```

131

132

**Usage Example:**

133

134

```typescript

135

import { onINP } from "web-vitals/attribution";

136

137

onINP((metric) => {

138

console.log('INP time:', metric.value + 'ms');

139

console.log('Slow interaction target:', metric.attribution.interactionTarget);

140

console.log('Interaction type:', metric.attribution.interactionType);

141

142

// Timing breakdown

143

console.log('Input delay:', metric.attribution.inputDelay + 'ms');

144

console.log('Processing time:', metric.attribution.processingDuration + 'ms');

145

console.log('Presentation delay:', metric.attribution.presentationDelay + 'ms');

146

147

// Identify bottleneck

148

const bottleneck = Math.max(

149

metric.attribution.inputDelay,

150

metric.attribution.processingDuration,

151

metric.attribution.presentationDelay

152

);

153

154

if (bottleneck === metric.attribution.processingDuration) {

155

console.log('Bottleneck: JavaScript processing time');

156

} else if (bottleneck === metric.attribution.presentationDelay) {

157

console.log('Bottleneck: Rendering/paint time');

158

} else {

159

console.log('Bottleneck: Input delay');

160

}

161

});

162

```

163

164

### LCP Attribution

165

166

Provides detailed information about the largest contentful paint element and loading phases.

167

168

```typescript { .api }

169

/**

170

* LCP measurement with attribution data for debugging loading performance

171

* @param callback - Function to receive LCP metric with attribution data

172

* @param opts - Optional configuration including custom target generation

173

*/

174

function onLCP(callback: (metric: LCPMetricWithAttribution) => void, opts?: AttributionReportOpts): void;

175

176

interface LCPMetricWithAttribution extends LCPMetric {

177

attribution: LCPAttribution;

178

}

179

180

interface LCPAttribution {

181

/** CSS selector of the LCP element */

182

target?: string;

183

/** URL of the LCP resource (for images) */

184

url?: string;

185

/** Time from page load initiation to first byte received (TTFB) */

186

timeToFirstByte: number;

187

/** Delta between TTFB and when browser starts loading LCP resource */

188

resourceLoadDelay: number;

189

/** Total time to load the LCP resource itself */

190

resourceLoadDuration: number;

191

/** Delta from resource load finish to LCP element fully rendered */

192

elementRenderDelay: number;

193

/** Navigation entry for diagnosing general page load issues */

194

navigationEntry?: PerformanceNavigationTiming;

195

/** Resource entry for the LCP resource for diagnosing load issues */

196

lcpResourceEntry?: PerformanceResourceTiming;

197

}

198

```

199

200

**Usage Example:**

201

202

```typescript

203

import { onLCP } from "web-vitals/attribution";

204

205

onLCP((metric) => {

206

console.log('LCP time:', metric.value + 'ms');

207

console.log('LCP element:', metric.attribution.element);

208

console.log('LCP resource URL:', metric.attribution.url);

209

210

// Loading phase breakdown

211

console.log('Time to resource start:', metric.attribution.timeToFirstByte + 'ms');

212

console.log('Resource load delay:', metric.attribution.resourceLoadDelay + 'ms');

213

console.log('Resource load duration:', metric.attribution.resourceLoadDuration + 'ms');

214

console.log('Element render delay:', metric.attribution.elementRenderDelay + 'ms');

215

216

// Identify optimization opportunities

217

if (metric.attribution.resourceLoadDelay > 100) {

218

console.log('Consider preloading the LCP resource');

219

}

220

if (metric.attribution.elementRenderDelay > 50) {

221

console.log('Consider optimizing render-blocking resources');

222

}

223

});

224

```

225

226

### FCP Attribution

227

228

Provides information about the first contentful paint and potential blocking resources.

229

230

```typescript { .api }

231

/**

232

* FCP measurement with attribution data for debugging first paint

233

* @param callback - Function to receive FCP metric with attribution data

234

* @param opts - Optional configuration including custom target generation

235

*/

236

function onFCP(callback: (metric: FCPMetricWithAttribution) => void, opts?: AttributionReportOpts): void;

237

238

interface FCPMetricWithAttribution extends FCPMetric {

239

attribution: FCPAttribution;

240

}

241

242

interface FCPAttribution {

243

/** Time from page load initiation to first byte received (TTFB) */

244

timeToFirstByte: number;

245

/** Delta between TTFB and first contentful paint */

246

firstByteToFCP: number;

247

/** Loading state when FCP occurred */

248

loadState: LoadState;

249

/** PerformancePaintTiming entry corresponding to FCP */

250

fcpEntry?: PerformancePaintTiming;

251

/** Navigation entry for diagnosing general page load issues */

252

navigationEntry?: PerformanceNavigationTiming;

253

}

254

```

255

256

### TTFB Attribution

257

258

Provides detailed breakdown of server response time components.

259

260

```typescript { .api }

261

/**

262

* TTFB measurement with attribution data for debugging server response

263

* @param callback - Function to receive TTFB metric with attribution data

264

* @param opts - Optional configuration including custom target generation

265

*/

266

function onTTFB(callback: (metric: TTFBMetricWithAttribution) => void, opts?: AttributionReportOpts): void;

267

268

interface TTFBMetricWithAttribution extends TTFBMetric {

269

attribution: TTFBAttribution;

270

}

271

272

interface TTFBAttribution {

273

/** Total time from user initiation to page start handling request */

274

waitingDuration: number;

275

/** Total time spent checking HTTP cache for a match */

276

cacheDuration: number;

277

/** Total time to resolve DNS for the requested domain */

278

dnsDuration: number;

279

/** Total time to create connection to the requested domain */

280

connectionDuration: number;

281

/** Time from request sent to first byte received (includes network + server time) */

282

requestDuration: number;

283

/** Navigation entry for diagnosing general page load issues */

284

navigationEntry?: PerformanceNavigationTiming;

285

}

286

```

287

288

**Usage Example:**

289

290

```typescript

291

import { onTTFB } from "web-vitals/attribution";

292

293

onTTFB((metric) => {

294

console.log('TTFB time:', metric.value + 'ms');

295

296

// Network timing breakdown

297

console.log('DNS lookup:', metric.attribution.dnsDuration + 'ms');

298

console.log('Connection time:', metric.attribution.connectionDuration + 'ms');

299

console.log('Server response:', metric.attribution.requestDuration + 'ms');

300

301

// Optimization suggestions

302

if (metric.attribution.dnsDuration > 20) {

303

console.log('Consider DNS prefetching or using a faster DNS resolver');

304

}

305

if (metric.attribution.connectionDuration > 100) {

306

console.log('Consider using HTTP/2 or reducing connection setup time');

307

}

308

if (metric.attribution.requestDuration > 600) {

309

console.log('Consider server-side optimizations or CDN usage');

310

}

311

});

312

```

313

314

## Configuration Options

315

316

### Custom Target Generation

317

318

The attribution build allows customizing how DOM elements are converted to CSS selectors for debugging.

319

320

```typescript { .api }

321

interface AttributionReportOpts extends ReportOpts {

322

/**

323

* Custom function to generate target selectors for DOM elements

324

* @param el - The DOM element to generate a selector for

325

* @returns A string selector, null, or undefined (falls back to default)

326

*/

327

generateTarget?: (el: Node | null) => string | null | undefined;

328

}

329

```

330

331

**Usage Example:**

332

333

```typescript

334

import { onCLS } from "web-vitals/attribution";

335

336

onCLS((metric) => {

337

console.log('Custom element selector:', metric.attribution.largestShiftTarget);

338

}, {

339

generateTarget: (element) => {

340

// Custom selector generation logic

341

if (element?.id) {

342

return `#${element.id}`;

343

}

344

if (element?.className) {

345

return `.${element.className.split(' ')[0]}`;

346

}

347

return element?.tagName?.toLowerCase() || 'unknown';

348

}

349

});

350

```

351

352

## Supported Types

353

354

```typescript { .api }

355

type LoadState = 'loading' | 'dom-interactive' | 'dom-content-loaded' | 'complete';

356

357

interface AttributionReportOpts extends ReportOpts {

358

generateTarget?: (el: Node | null) => string | null | undefined;

359

}

360

361

interface INPAttributionReportOpts extends AttributionReportOpts {

362

durationThreshold?: number;

363

}

364

```