迁移范围:lesson-preparation 11 文件、attendance 3 文件、settings 4 文件、classes-invitations、跨模块接口 4 文件、adaptive-practice/ai/onboarding/invitation-codes/search/standards/scheduling/leave-requests/audit。修复 3 个 cacheFn 块位置错误。同步架构文档迁移范围至 83 文件 250+ 函数。
74 lines
1.8 KiB
TypeScript
74 lines
1.8 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
|
|
/**
|
|
* Web Vitals 上报端点
|
|
*
|
|
* 接收前端 navigator.sendBeacon 上报的 LCP/CLS/INP/FCP/TTFP 指标,
|
|
* 当前实现:写入服务端日志(便于开发期查看),生产可接入监控系统。
|
|
*
|
|
* 性能预算基线(见 docs/architecture/audit/performance-budget-audit-report.md):
|
|
* - LCP ≤ 2.5s,INP ≤ 200ms,CLS ≤ 0.05,TTFB ≤ 800ms,FCP ≤ 1.8s
|
|
* - 超出预算的 "poor" 级别会以 warn 级别日志记录便于排查
|
|
*/
|
|
export const runtime = "edge";
|
|
|
|
type WebVitalEntry = {
|
|
name: string;
|
|
startTime: number;
|
|
duration: number;
|
|
entryType: string;
|
|
};
|
|
|
|
type WebVitalsPayload = {
|
|
id: string;
|
|
name: string;
|
|
value: number;
|
|
rating: "good" | "needs-improvement" | "poor";
|
|
delta: number;
|
|
entries: WebVitalEntry[];
|
|
path: string;
|
|
timestamp: number;
|
|
};
|
|
|
|
// 性能预算基线,超出即标记为 poor
|
|
const BUDGETS: Record<string, number> = {
|
|
LCP: 2500,
|
|
INP: 200,
|
|
CLS: 0.05,
|
|
FCP: 1800,
|
|
TTFB: 800,
|
|
};
|
|
|
|
export async function POST(request: Request): Promise<NextResponse> {
|
|
let payload: WebVitalsPayload;
|
|
try {
|
|
payload = (await request.json()) as WebVitalsPayload;
|
|
} catch {
|
|
return NextResponse.json({ ok: false }, { status: 400 });
|
|
}
|
|
|
|
const budget = BUDGETS[payload.name];
|
|
const isOverBudget = budget !== undefined && payload.value > budget;
|
|
|
|
// good/info 级别用 info,rating poor 或超预算用 warn
|
|
if (payload.rating === "poor" || isOverBudget) {
|
|
console.warn("[web-vitals] over-budget", {
|
|
name: payload.name,
|
|
value: payload.value,
|
|
rating: payload.rating,
|
|
budget,
|
|
path: payload.path,
|
|
id: payload.id,
|
|
});
|
|
} else {
|
|
console.info("[web-vitals] reported", {
|
|
name: payload.name,
|
|
value: payload.value,
|
|
rating: payload.rating,
|
|
path: payload.path,
|
|
});
|
|
}
|
|
|
|
return NextResponse.json({ ok: true });
|
|
}
|