From b7e52aa0867976668575f99934e41cb9d33e7ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Fri, 13 Feb 2026 15:24:51 +0800 Subject: [PATCH] perf: add web vitals monitoring and optimize performance --- next.config.ts | 7 ++++++ src/app/layout.tsx | 21 ++++++++++++++-- src/components/analytics/web-vitals.tsx | 33 +++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/components/analytics/web-vitals.tsx diff --git a/next.config.ts b/next.config.ts index b6c8d96..274b130 100644 --- a/next.config.ts +++ b/next.config.ts @@ -7,6 +7,13 @@ const nextConfig: NextConfig = { unoptimized: true, deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], + formats: ['image/webp'], + }, + compress: true, + poweredByHeader: false, + reactStrictMode: true, + experimental: { + optimizePackageImports: ['lucide-react', 'framer-motion'], }, }; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0dc1e35..0c3ec9b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,23 +1,29 @@ -import type { Metadata } from "next"; +import type { Metadata, Viewport } from "next"; import { Geist, Geist_Mono, Noto_Sans_SC } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "@/contexts/theme-context"; +import { WebVitals } from "@/components/analytics/web-vitals"; const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], + display: "swap", + preload: true, }); const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], + display: "swap", + preload: true, }); -// 思源黑体 - 中文字体 const notoSansSC = Noto_Sans_SC({ weight: ["400", "500", "700"], variable: "--font-noto-sans-sc", subsets: ["latin"], + display: "swap", + preload: true, }); export const metadata: Metadata = { @@ -52,6 +58,16 @@ export const metadata: Metadata = { }, }; +export const viewport: Viewport = { + width: "device-width", + initialScale: 1, + maximumScale: 5, + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "#FFFFFF" }, + { media: "(prefers-color-scheme: dark)", color: "#0A0A0A" }, + ], +}; + export default function RootLayout({ children, }: Readonly<{ @@ -79,6 +95,7 @@ export default function RootLayout({ className={`${geistSans.variable} ${geistMono.variable} ${notoSansSC.variable} font-sans antialiased`} style={{ fontFamily: "'Noto Sans SC', 'Geist', -apple-system, BlinkMacSystemFont, sans-serif" }} > + {children} diff --git a/src/components/analytics/web-vitals.tsx b/src/components/analytics/web-vitals.tsx new file mode 100644 index 0000000..8dfd445 --- /dev/null +++ b/src/components/analytics/web-vitals.tsx @@ -0,0 +1,33 @@ +'use client'; + +import { useReportWebVitals } from 'next/web-vitals'; + +export function WebVitals() { + useReportWebVitals((metric) => { + if (process.env.NODE_ENV === 'development') { + console.log('[Web Vitals]', metric); + } + + if (process.env.NODE_ENV === 'production') { + const body = JSON.stringify({ + name: metric.name, + value: metric.value, + rating: metric.rating, + delta: metric.delta, + id: metric.id, + }); + + if (navigator.sendBeacon) { + navigator.sendBeacon('/api/analytics', body); + } else { + fetch('/api/analytics', { + body, + method: 'POST', + keepalive: true, + }).catch(() => {}); + } + } + }); + + return null; +}