Web vitals: prefer external RUM
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { logger } from "../../logger";
|
||||
|
||||
const WEB_VITALS_DIR = path.join(process.cwd(), ".next", "web-vitals");
|
||||
|
||||
export interface WebVitalData {
|
||||
metric: string;
|
||||
data: {
|
||||
value: number;
|
||||
rating: string;
|
||||
};
|
||||
url: string;
|
||||
userAgent: string;
|
||||
timestamp: string;
|
||||
receivedAt: string;
|
||||
}
|
||||
|
||||
export interface WebVitalMetrics {
|
||||
[metric: string]: {
|
||||
count: number;
|
||||
average: number;
|
||||
min: number;
|
||||
max: number;
|
||||
goodCount: number;
|
||||
needsImprovementCount: number;
|
||||
poorCount: number;
|
||||
lastUpdated: string;
|
||||
};
|
||||
}
|
||||
|
||||
function ensureWebVitalsDir(): void {
|
||||
if (!fs.existsSync(WEB_VITALS_DIR)) {
|
||||
fs.mkdirSync(WEB_VITALS_DIR, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function appendLocalWebVital(vitalsData: WebVitalData): void {
|
||||
ensureWebVitalsDir();
|
||||
const filePath = path.join(WEB_VITALS_DIR, `${vitalsData.metric}.json`);
|
||||
let existingData: WebVitalData[] = [];
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
try {
|
||||
const fileContent = fs.readFileSync(filePath, "utf8");
|
||||
existingData = JSON.parse(fileContent) as WebVitalData[];
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
logger.warn("Could not parse existing vitals data:", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
existingData.push(vitalsData);
|
||||
|
||||
if (existingData.length > 100) {
|
||||
existingData = existingData.slice(-100);
|
||||
}
|
||||
|
||||
fs.writeFileSync(filePath, JSON.stringify(existingData, null, 2));
|
||||
}
|
||||
|
||||
export function readLocalAggregatedMetrics(): WebVitalMetrics {
|
||||
const metrics: WebVitalMetrics = {};
|
||||
|
||||
if (!fs.existsSync(WEB_VITALS_DIR)) {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(WEB_VITALS_DIR);
|
||||
|
||||
files.forEach((file) => {
|
||||
if (!file.endsWith(".json")) return;
|
||||
const metric = file.replace(".json", "");
|
||||
let data: WebVitalData[];
|
||||
try {
|
||||
const fileContent = fs.readFileSync(
|
||||
path.join(WEB_VITALS_DIR, file),
|
||||
"utf8",
|
||||
);
|
||||
data = JSON.parse(fileContent) as WebVitalData[];
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`Skipping corrupt web vitals file ${file}:`,
|
||||
(error as Error).message,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.length === 0) return;
|
||||
|
||||
const values = data
|
||||
.map((d) => d.data.value)
|
||||
.filter((v) => v !== undefined);
|
||||
const ratings = data
|
||||
.map((d) => d.data.rating)
|
||||
.filter((r) => r !== undefined);
|
||||
|
||||
metrics[metric] = {
|
||||
count: data.length,
|
||||
average:
|
||||
values.length > 0
|
||||
? Math.round(values.reduce((a, b) => a + b, 0) / values.length)
|
||||
: 0,
|
||||
min: values.length > 0 ? Math.min(...values) : 0,
|
||||
max: values.length > 0 ? Math.max(...values) : 0,
|
||||
goodCount: ratings.filter((r) => r === "good").length,
|
||||
needsImprovementCount: ratings.filter((r) => r === "needs-improvement")
|
||||
.length,
|
||||
poorCount: ratings.filter((r) => r === "poor").length,
|
||||
lastUpdated: data[data.length - 1]?.receivedAt || "",
|
||||
};
|
||||
});
|
||||
|
||||
return metrics;
|
||||
}
|
||||
Reference in New Issue
Block a user