Collecting and Sending Web Vitals to Kafka from React Applications
- Mark Kendall
- Feb 1
- 5 min read
## Collecting and Sending Web Vitals to Kafka from React Applications
This article provides a comprehensive guide on how to collect Core Web Vitals in your React application and send them to a Kafka cluster for analysis and monitoring. We'll cover setting up the necessary tools, instrumenting your React code, configuring a Spring Boot backend to receive and forward the vitals to Kafka, and consuming the messages.
### What are Core Web Vitals?
Core Web Vitals are a set of metrics that measure the user experience of your web application. They are essential for understanding how your site performs from a user's perspective and are a ranking factor for search engines. The key vitals are:
*LCP (Largest Contentful Paint):** Measures how long it takes for the largest content element (image, text block, etc.) to become visible within the viewport.
*FID (First Input Delay):** Measures the time from when a user first interacts with your site (e.g., clicks a button) to the time when the browser is actually able to begin processing that interaction.
*CLS (Cumulative Layout Shift):** A measure of how much the layout of the page shifts unexpectedly.
### Setting up the Project
1. React Application: Ensure you have a React project set up. You can create one using Create React App:
```bash
npx create-react-app my-web-vitals-app --template typescript
cd my-web-vitals-app
```
2. Install `web-vitals`:
```bash
npm install web-vitals
```
3. Spring Boot Backend: You'll need a backend service to act as an intermediary between your React app and Kafka. We'll use Spring Boot for this. Create a new Spring Boot project (e.g., using Spring Initializr or your IDE). Add the following dependencies to your `pom.xml`:
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
```
4. Kafka Cluster: You'll need a running Kafka cluster. You can set up a local cluster using Docker or use a managed Kafka service in the cloud.
### Instrumenting the React Application
1. Create `webVitalsModule.ts`: Create a file named `webVitalsModule.ts` in your React project:
```typescript
import { getCLS, getFID, getFCP, getLCP, getTTFB, Metric } from 'web-vitals';
interface WebVitalsData {
cls: number | undefined;
fid: number | undefined;
fcp: number | undefined;
lcp: number | undefined;
ttfb: number | undefined;
branch: string;
environment: string;
component: string;
timestamp: number;
}
async function sendWebVitalsToKafka(metric: Metric, componentName: string): Promise<void> {
try {
const vitals: WebVitalsData = {
cls: metric.name === 'CLS' ? metric.value : undefined,
fid: metric.name === 'FID' ? metric.value : undefined,
fcp: metric.name === 'FCP' ? metric.value : undefined,
lcp: metric.name === 'LCP' ? metric.value : undefined,
ttfb: metric.name === 'TTFB' ? metric.value : undefined,
branch: process.env.REACT_APP_BRANCH || 'development',
environment: process.env.REACT_APP_ENVIRONMENT || 'development',
component: componentName,
timestamp: Date.now(),
};
const response = await fetch('/your-kafka-endpoint', { // Replace with your Spring Boot endpoint
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(vitals),
});
if (!response.ok) {
console.error('Failed to send web vitals:', response.status);
}
} catch (error) {
console.error('Error sending web vitals:', error);
}
}
function collectWebVitals(componentName: string): void {
getCLS((metric) => sendWebVitalsToKafka(metric, componentName));
getFID((metric) => sendWebVitalsToKafka(metric, componentName));
getFCP((metric) => sendWebVitalsToKafka(metric, componentName));
getLCP((metric) => sendWebVitalsToKafka(metric, componentName));
getTTFB((metric) => sendWebVitalsToKafka(metric, componentName));
}
export { collectWebVitals };
```
2. Use in React Components: Import `collectWebVitals` into your components and call it within a `useEffect` hook, passing the component name:
```typescript
import React, { useEffect } from 'react';
import { collectWebVitals } from './webVitalsModule';
function MyComponent() {
useEffect(() => {
collectWebVitals("MyComponent");
}, []);
// ... your component code
return (<div>My Component</div>);
}
export default MyComponent;
```
### Spring Boot Backend
1. `WebVitalsData` POJO: Create a class to represent the web vitals data:
```java
import lombok.Data;
@Data
public class WebVitalsData {
private Double cls;
private Double fid;
private Double fcp;
private Double lcp;
private Double ttfb;
private String branch;
private String environment;
private String component;
private Long timestamp;
}
```
2. `WebVitalsController`: Create a controller to handle the incoming POST requests from your React app:
```java
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
public class WebVitalsController {
private final KafkaTemplate<String, WebVitalsData> kafkaTemplate;
public WebVitalsController(KafkaTemplate<String, WebVitalsData> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
@PostMapping("/your-kafka-endpoint")
public void sendWebVitals(@RequestBody WebVitalsData vitals) {
log.info("Received vitals: {}", vitals);
kafkaTemplate.send("web-vitals-topic", vitals);
}
}
```
3. `KafkaConfig`: Configure the Kafka producer:
```java
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.serializer.JsonSerializer;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class KafkaConfig {
private final KafkaProperties kafkaProperties;
public KafkaConfig(KafkaProperties kafkaProperties) {
this.kafkaProperties = kafkaProperties;
}
@Bean
public KafkaTemplate<String, WebVitalsData> kafkaTemplate() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaProperties.getBootstrapServers());
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
return new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(props));
}
}
```
4. `WebVitalsConsumer`: Create a consumer to read messages from Kafka:
```java
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Comentários