The /health route returns JSON status for Docker Swarm to verify container health during deployment. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
754 lines
17 KiB
Markdown
754 lines
17 KiB
Markdown
# Performance Checklist for Mylder Platform
|
|
|
|
## Core Web Vitals Targets
|
|
|
|
### Production Goals (75th Percentile)
|
|
|
|
| Metric | Target | Good | Needs Improvement | Poor |
|
|
|--------|--------|------|-------------------|------|
|
|
| **LCP** (Largest Contentful Paint) | <2.5s | <2.5s | 2.5s-4.0s | >4.0s |
|
|
| **FID** (First Input Delay) | <100ms | <100ms | 100ms-300ms | >300ms |
|
|
| **CLS** (Cumulative Layout Shift) | <0.1 | <0.1 | 0.1-0.25 | >0.25 |
|
|
| **INP** (Interaction to Next Paint) | <200ms | <200ms | 200ms-500ms | >500ms |
|
|
| **TTFB** (Time to First Byte) | <600ms | <800ms | 800ms-1800ms | >1800ms |
|
|
| **FCP** (First Contentful Paint) | <1.8s | <1.8s | 1.8s-3.0s | >3.0s |
|
|
|
|
### Current vs Target Performance
|
|
|
|
**Baseline (before optimization):**
|
|
```
|
|
LCP: ~3.5s → Target: <2.5s (30% improvement needed)
|
|
FID: ~150ms → Target: <100ms
|
|
CLS: ~0.15 → Target: <0.1
|
|
TTFB: ~800ms → Target: <600ms (edge caching)
|
|
```
|
|
|
|
### Measurement Tools
|
|
|
|
#### 1. Real User Monitoring (RUM)
|
|
```typescript
|
|
// app/layout.tsx
|
|
import { SpeedInsights } from '@vercel/speed-insights/next';
|
|
import { Analytics } from '@vercel/analytics/react';
|
|
|
|
export default function RootLayout({ children }) {
|
|
return (
|
|
<html>
|
|
<body>
|
|
{children}
|
|
<SpeedInsights />
|
|
<Analytics />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Alternative:** Cloudflare Web Analytics (privacy-friendly, no cookies)
|
|
```html
|
|
<script defer src='https://static.cloudflareinsights.com/beacon.min.js'
|
|
data-cf-beacon='{"token": "YOUR_TOKEN"}'></script>
|
|
```
|
|
|
|
#### 2. Lab Testing
|
|
```bash
|
|
# Lighthouse (local)
|
|
npx lighthouse https://mylder.io --view
|
|
|
|
# WebPageTest (global)
|
|
# https://www.webpagetest.org
|
|
|
|
# Chrome DevTools
|
|
# DevTools → Performance → Record → Analyze
|
|
```
|
|
|
|
#### 3. Continuous Monitoring
|
|
```bash
|
|
# Lighthouse CI (in CI/CD pipeline)
|
|
npm install -g @lhci/cli
|
|
lhci autorun --collect.url=https://mylder.io
|
|
```
|
|
|
|
## Next.js Optimization Settings
|
|
|
|
### 1. Production Build Configuration
|
|
|
|
```typescript
|
|
// next.config.ts
|
|
import type { NextConfig } from 'next';
|
|
|
|
const nextConfig: NextConfig = {
|
|
// Enable React strict mode for better error handling
|
|
reactStrictMode: true,
|
|
|
|
// Optimize bundle size
|
|
swcMinify: true,
|
|
|
|
// Compress output
|
|
compress: true,
|
|
|
|
// Optimize images
|
|
images: {
|
|
formats: ['image/avif', 'image/webp'],
|
|
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
|
|
imageSizes: [16, 32, 48, 64, 96, 128, 256],
|
|
minimumCacheTTL: 2592000, // 30 days
|
|
dangerouslyAllowSVG: false,
|
|
contentDispositionType: 'attachment',
|
|
remotePatterns: [
|
|
{
|
|
protocol: 'https',
|
|
hostname: 'supabase.mylder.io',
|
|
pathname: '/storage/v1/object/**',
|
|
},
|
|
],
|
|
},
|
|
|
|
// Optimize fonts (automatic optimization)
|
|
optimizeFonts: true,
|
|
|
|
// Experimental features
|
|
experimental: {
|
|
// Optimize CSS
|
|
optimizeCss: true,
|
|
// Tree shaking for smaller bundles
|
|
optimizePackageImports: ['@radix-ui/react-icons', 'lucide-react'],
|
|
},
|
|
|
|
// Custom headers for caching
|
|
async headers() {
|
|
return [
|
|
{
|
|
source: '/:path*',
|
|
headers: [
|
|
{
|
|
key: 'X-DNS-Prefetch-Control',
|
|
value: 'on',
|
|
},
|
|
{
|
|
key: 'X-Frame-Options',
|
|
value: 'SAMEORIGIN',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
source: '/_next/static/:path*',
|
|
headers: [
|
|
{
|
|
key: 'Cache-Control',
|
|
value: 'public, max-age=31536000, immutable',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
source: '/_next/image/:path*',
|
|
headers: [
|
|
{
|
|
key: 'Cache-Control',
|
|
value: 'public, max-age=2592000, stale-while-revalidate=86400',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
},
|
|
};
|
|
|
|
export default nextConfig;
|
|
```
|
|
|
|
### 2. Bundle Analyzer
|
|
```bash
|
|
# Install
|
|
npm install -D @next/bundle-analyzer
|
|
|
|
# next.config.ts
|
|
import bundleAnalyzer from '@next/bundle-analyzer';
|
|
|
|
const withBundleAnalyzer = bundleAnalyzer({
|
|
enabled: process.env.ANALYZE === 'true',
|
|
});
|
|
|
|
export default withBundleAnalyzer(nextConfig);
|
|
|
|
# Run analysis
|
|
ANALYZE=true npm run build
|
|
```
|
|
|
|
**Target:** Total bundle size <500KB (gzipped)
|
|
|
|
### 3. Code Splitting Strategies
|
|
|
|
#### Dynamic Imports
|
|
```typescript
|
|
// app/dashboard/page.tsx
|
|
import dynamic from 'next/dynamic';
|
|
|
|
// Lazy load heavy components
|
|
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
|
|
loading: () => <ChartSkeleton />,
|
|
ssr: false, // Client-side only if needed
|
|
});
|
|
|
|
export default function Dashboard() {
|
|
return <HeavyChart data={data} />;
|
|
}
|
|
```
|
|
|
|
#### Route-Based Splitting
|
|
```typescript
|
|
// Automatic with App Router
|
|
app/
|
|
dashboard/ # Separate chunk
|
|
settings/ # Separate chunk
|
|
(marketing)/ # Separate chunk (route group)
|
|
```
|
|
|
|
#### Vendor Splitting
|
|
```typescript
|
|
// next.config.ts
|
|
experimental: {
|
|
optimizePackageImports: [
|
|
'@radix-ui/react-icons',
|
|
'lucide-react',
|
|
'date-fns',
|
|
],
|
|
},
|
|
```
|
|
|
|
### 4. Server Components (Default)
|
|
```typescript
|
|
// app/page.tsx (Server Component by default)
|
|
export default async function Home() {
|
|
// Fetch at build time or request time
|
|
const data = await fetch('https://api.mylder.io/data', {
|
|
next: { revalidate: 3600 } // ISR: 1 hour cache
|
|
});
|
|
|
|
return <div>{/* No client-side JS for this component */}</div>;
|
|
}
|
|
```
|
|
|
|
**Benefits:**
|
|
- 0KB JavaScript sent to client
|
|
- Faster initial page load
|
|
- Better SEO
|
|
|
|
### 5. Client Components (Opt-In)
|
|
```typescript
|
|
// components/InteractiveButton.tsx
|
|
'use client';
|
|
|
|
import { useState } from 'react';
|
|
|
|
export default function InteractiveButton() {
|
|
const [count, setCount] = useState(0);
|
|
return <button onClick={() => setCount(count + 1)}>{count}</button>;
|
|
}
|
|
```
|
|
|
|
**Rule:** Only use 'use client' when you need:
|
|
- Interactivity (useState, useEffect)
|
|
- Browser APIs (localStorage, window)
|
|
- Event handlers (onClick, onSubmit)
|
|
|
|
## Image Optimization
|
|
|
|
### 1. Next.js Image Component
|
|
```typescript
|
|
import Image from 'next/image';
|
|
|
|
// Optimized image with automatic sizing
|
|
<Image
|
|
src="/hero.jpg"
|
|
alt="Hero image"
|
|
width={1920}
|
|
height={1080}
|
|
priority // For LCP images
|
|
placeholder="blur"
|
|
blurDataURL="data:image/jpeg;base64,/9j/..." // Generated at build
|
|
/>
|
|
|
|
// Responsive image
|
|
<Image
|
|
src="/hero.jpg"
|
|
alt="Hero image"
|
|
fill
|
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
|
className="object-cover"
|
|
priority
|
|
/>
|
|
```
|
|
|
|
### 2. Image Format Priority
|
|
1. **AVIF** (best compression, 50% smaller than JPEG)
|
|
2. **WebP** (wide support, 30% smaller than JPEG)
|
|
3. **JPEG/PNG** (fallback)
|
|
|
|
### 3. Cloudflare Image Optimization
|
|
```typescript
|
|
// next.config.ts
|
|
images: {
|
|
loader: 'custom',
|
|
loaderFile: './lib/cloudflare-image-loader.ts',
|
|
}
|
|
|
|
// lib/cloudflare-image-loader.ts
|
|
export default function cloudflareLoader({ src, width, quality }) {
|
|
const params = [`width=${width}`, `quality=${quality || 75}`, 'format=auto'];
|
|
return `https://mylder.io/cdn-cgi/image/${params.join(',')}/${src}`;
|
|
}
|
|
```
|
|
|
|
### 4. Lazy Loading Images
|
|
```typescript
|
|
// Non-critical images (below fold)
|
|
<Image
|
|
src="/feature.jpg"
|
|
alt="Feature"
|
|
width={800}
|
|
height={600}
|
|
loading="lazy" // Default, explicit here
|
|
/>
|
|
|
|
// Critical images (above fold, LCP)
|
|
<Image
|
|
src="/hero.jpg"
|
|
alt="Hero"
|
|
width={1920}
|
|
height={1080}
|
|
priority // Preload immediately
|
|
/>
|
|
```
|
|
|
|
### 5. Responsive Images Checklist
|
|
- [ ] Use Next.js Image component (automatic optimization)
|
|
- [ ] Set explicit width/height (prevent CLS)
|
|
- [ ] Use `priority` for LCP images
|
|
- [ ] Use `loading="lazy"` for below-fold images
|
|
- [ ] Provide blur placeholder for better UX
|
|
- [ ] Compress images before upload (80-85% quality)
|
|
- [ ] Use modern formats (AVIF, WebP)
|
|
|
|
## Font Optimization
|
|
|
|
### 1. Next.js Font Optimization (Built-In)
|
|
```typescript
|
|
// app/layout.tsx
|
|
import { Inter, Roboto_Mono } from 'next/font/google';
|
|
|
|
const inter = Inter({
|
|
subsets: ['latin'],
|
|
display: 'swap', // Prevent invisible text flash
|
|
variable: '--font-inter',
|
|
preload: true,
|
|
});
|
|
|
|
const robotoMono = Roboto_Mono({
|
|
subsets: ['latin'],
|
|
display: 'swap',
|
|
variable: '--font-roboto-mono',
|
|
weight: ['400', '700'], // Only load needed weights
|
|
});
|
|
|
|
export default function RootLayout({ children }) {
|
|
return (
|
|
<html className={`${inter.variable} ${robotoMono.variable}`}>
|
|
<body className="font-sans">{children}</body>
|
|
</html>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Benefits:**
|
|
- Self-hosted fonts (no external requests)
|
|
- Automatic subset optimization
|
|
- Zero layout shift (font-display: swap)
|
|
|
|
### 2. Custom Fonts
|
|
```typescript
|
|
import localFont from 'next/font/local';
|
|
|
|
const myFont = localFont({
|
|
src: './fonts/MyFont.woff2',
|
|
display: 'swap',
|
|
variable: '--font-my-font',
|
|
});
|
|
```
|
|
|
|
### 3. Font Loading Best Practices
|
|
- [ ] Use only 2-3 font families max
|
|
- [ ] Load only needed weights (400, 700)
|
|
- [ ] Use `font-display: swap`
|
|
- [ ] Preload critical fonts
|
|
- [ ] Self-host fonts (no Google Fonts CDN)
|
|
|
|
## CSS Optimization
|
|
|
|
### 1. Tailwind CSS Production Config
|
|
```typescript
|
|
// tailwind.config.ts
|
|
import type { Config } from 'tailwindcss';
|
|
|
|
const config: Config = {
|
|
content: [
|
|
'./app/**/*.{ts,tsx}',
|
|
'./components/**/*.{ts,tsx}',
|
|
],
|
|
theme: {
|
|
extend: {},
|
|
},
|
|
// Remove unused styles in production
|
|
future: {
|
|
hoverOnlyWhenSupported: true, // Better mobile UX
|
|
},
|
|
};
|
|
|
|
export default config;
|
|
```
|
|
|
|
### 2. Critical CSS
|
|
Next.js automatically inlines critical CSS for first paint.
|
|
|
|
### 3. CSS Module Best Practices
|
|
```typescript
|
|
// Use CSS Modules for component-specific styles
|
|
import styles from './Button.module.css';
|
|
|
|
export function Button() {
|
|
return <button className={styles.button}>Click me</button>;
|
|
}
|
|
```
|
|
|
|
## JavaScript Optimization
|
|
|
|
### 1. Tree Shaking
|
|
```typescript
|
|
// Good: Import only what you need
|
|
import { format } from 'date-fns';
|
|
|
|
// Bad: Import entire library
|
|
import * as dateFns from 'date-fns';
|
|
```
|
|
|
|
### 2. Remove Unused Dependencies
|
|
```bash
|
|
# Analyze bundle
|
|
npx depcheck
|
|
|
|
# Remove unused
|
|
npm uninstall unused-package
|
|
```
|
|
|
|
### 3. Use Smaller Alternatives
|
|
|
|
| Heavy Package | Lightweight Alternative | Savings |
|
|
|---------------|------------------------|---------|
|
|
| Moment.js (232KB) | date-fns (13KB) | 95% |
|
|
| Lodash (70KB) | Lodash-es (24KB) | 66% |
|
|
| Axios (30KB) | Fetch API (native) | 100% |
|
|
| React Icons (full) | Lucide React (tree-shakeable) | 80% |
|
|
|
|
### 4. Debounce/Throttle Heavy Operations
|
|
```typescript
|
|
// components/Search.tsx
|
|
'use client';
|
|
|
|
import { useState, useCallback } from 'react';
|
|
import { debounce } from 'lodash-es/debounce';
|
|
|
|
export function Search() {
|
|
const [query, setQuery] = useState('');
|
|
|
|
const handleSearch = useCallback(
|
|
debounce(async (value: string) => {
|
|
const results = await fetch(`/api/search?q=${value}`);
|
|
// Handle results
|
|
}, 300),
|
|
[]
|
|
);
|
|
|
|
return <input onChange={(e) => handleSearch(e.target.value)} />;
|
|
}
|
|
```
|
|
|
|
## Caching Strategy
|
|
|
|
### 1. Static Generation (Fastest)
|
|
```typescript
|
|
// app/blog/page.tsx
|
|
export const revalidate = false; // Static at build time
|
|
|
|
export default async function Blog() {
|
|
const posts = await fetchPosts();
|
|
return <PostList posts={posts} />;
|
|
}
|
|
```
|
|
|
|
### 2. Incremental Static Regeneration (ISR)
|
|
```typescript
|
|
// app/blog/[slug]/page.tsx
|
|
export const revalidate = 3600; // Revalidate every hour
|
|
|
|
export async function generateStaticParams() {
|
|
const posts = await fetchPosts();
|
|
return posts.map((post) => ({ slug: post.slug }));
|
|
}
|
|
|
|
export default async function Post({ params }) {
|
|
const post = await fetchPost(params.slug);
|
|
return <Article post={post} />;
|
|
}
|
|
```
|
|
|
|
### 3. Server-Side Rendering (SSR)
|
|
```typescript
|
|
// app/dashboard/page.tsx
|
|
export const dynamic = 'force-dynamic'; // Always server-render
|
|
|
|
export default async function Dashboard() {
|
|
const data = await fetchUserData(); // Per-request
|
|
return <DashboardContent data={data} />;
|
|
}
|
|
```
|
|
|
|
### 4. Client-Side Caching
|
|
```typescript
|
|
// Use SWR or React Query for client-side data fetching
|
|
'use client';
|
|
|
|
import useSWR from 'swr';
|
|
|
|
export function Profile() {
|
|
const { data, error } = useSWR('/api/user', fetcher, {
|
|
revalidateOnFocus: false,
|
|
revalidateOnReconnect: false,
|
|
refreshInterval: 60000, // 1 minute
|
|
});
|
|
|
|
if (error) return <div>Error</div>;
|
|
if (!data) return <div>Loading...</div>;
|
|
return <div>{data.name}</div>;
|
|
}
|
|
```
|
|
|
|
### 5. HTTP Cache Headers
|
|
```typescript
|
|
// app/api/data/route.ts
|
|
export async function GET() {
|
|
const data = await fetchData();
|
|
|
|
return new Response(JSON.stringify(data), {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
## Cache Invalidation Strategy
|
|
|
|
### 1. On-Demand Revalidation
|
|
```typescript
|
|
// app/api/revalidate/route.ts
|
|
import { revalidatePath, revalidateTag } from 'next/cache';
|
|
|
|
export async function POST(request: Request) {
|
|
const { path, tag } = await request.json();
|
|
|
|
if (path) {
|
|
revalidatePath(path);
|
|
}
|
|
|
|
if (tag) {
|
|
revalidateTag(tag);
|
|
}
|
|
|
|
return Response.json({ revalidated: true });
|
|
}
|
|
```
|
|
|
|
### 2. Webhook Triggers (n8n)
|
|
```typescript
|
|
// n8n workflow: CMS Update → POST /api/revalidate
|
|
{
|
|
"path": "/blog",
|
|
"tag": "posts"
|
|
}
|
|
```
|
|
|
|
### 3. Cloudflare Cache Purge
|
|
```bash
|
|
# On deploy (CI/CD)
|
|
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
|
|
-H "Authorization: Bearer {api_token}" \
|
|
-H "Content-Type: application/json" \
|
|
--data '{"purge_everything":true}'
|
|
```
|
|
|
|
### 4. Selective Purge
|
|
```bash
|
|
# Purge specific URLs
|
|
--data '{"files":["https://mylder.io/blog","https://mylder.io/about"]}'
|
|
|
|
# Purge by tag
|
|
--data '{"tags":["blog-posts"]}'
|
|
```
|
|
|
|
## Performance Testing Workflow
|
|
|
|
### 1. Pre-Deploy Checks
|
|
```bash
|
|
# Run before every deployment
|
|
npm run build # Must succeed
|
|
npm run lint # No errors
|
|
npm run typecheck # No type errors
|
|
|
|
# Optional but recommended
|
|
ANALYZE=true npm run build # Check bundle size
|
|
npx lighthouse https://staging.mylder.io --view
|
|
```
|
|
|
|
### 2. Post-Deploy Verification
|
|
```bash
|
|
# Production smoke tests
|
|
curl -I https://mylder.io # Check TTFB
|
|
curl -I https://mylder.io/_next/static/[hash]/[file].js # Check caching
|
|
|
|
# Full performance audit
|
|
npx lighthouse https://mylder.io --view
|
|
|
|
# Real user monitoring
|
|
# Check Cloudflare Analytics or Vercel Analytics dashboard
|
|
```
|
|
|
|
### 3. Continuous Monitoring
|
|
```bash
|
|
# Set up alerts in Cloudflare/monitoring tool
|
|
- LCP > 3s → Alert
|
|
- Error rate > 1% → Alert
|
|
- TTFB > 1s → Alert
|
|
```
|
|
|
|
## Quick Wins Checklist
|
|
|
|
### Immediate (1 day)
|
|
- [ ] Enable Cloudflare CDN (see cloudflare-setup.md)
|
|
- [ ] Add proper cache headers to static assets
|
|
- [ ] Use Next.js Image component everywhere
|
|
- [ ] Enable `reactStrictMode` and `swcMinify`
|
|
- [ ] Remove unused dependencies
|
|
|
|
### Short-term (1 week)
|
|
- [ ] Implement ISR for blog/content pages
|
|
- [ ] Lazy load heavy components
|
|
- [ ] Optimize images (compress, convert to WebP/AVIF)
|
|
- [ ] Set up bundle analyzer
|
|
- [ ] Add loading states and skeletons (improve perceived performance)
|
|
|
|
### Medium-term (1 month)
|
|
- [ ] Implement edge caching for auth (see cloudflare-workers-roadmap.md)
|
|
- [ ] Set up real user monitoring (RUM)
|
|
- [ ] Optimize third-party scripts (defer/async)
|
|
- [ ] Implement stale-while-revalidate for APIs
|
|
- [ ] Add Lighthouse CI to deployment pipeline
|
|
|
|
### Long-term (3 months)
|
|
- [ ] Move 70% traffic to edge (Cloudflare Workers)
|
|
- [ ] Implement edge personalization
|
|
- [ ] Set up A/B testing at edge
|
|
- [ ] Achieve Core Web Vitals targets consistently
|
|
- [ ] Reduce VPS load by 60%+
|
|
|
|
## Common Performance Issues & Fixes
|
|
|
|
### Issue: High LCP (>4s)
|
|
**Causes:**
|
|
- Large hero image not optimized
|
|
- Server response time (TTFB) too high
|
|
- Render-blocking resources
|
|
|
|
**Fixes:**
|
|
- Use Next.js Image with `priority` for LCP image
|
|
- Enable Cloudflare CDN for faster TTFB
|
|
- Inline critical CSS
|
|
- Preload fonts
|
|
|
|
### Issue: High CLS (>0.25)
|
|
**Causes:**
|
|
- Images without width/height
|
|
- Web fonts loading late
|
|
- Dynamic content injections
|
|
|
|
**Fixes:**
|
|
- Set explicit dimensions on images
|
|
- Use `font-display: swap`
|
|
- Reserve space for dynamic content
|
|
- Avoid inserting content above existing content
|
|
|
|
### Issue: High FID/INP (>300ms)
|
|
**Causes:**
|
|
- Heavy JavaScript execution
|
|
- Long tasks blocking main thread
|
|
- Unoptimized event handlers
|
|
|
|
**Fixes:**
|
|
- Code split large bundles
|
|
- Debounce/throttle event handlers
|
|
- Use Web Workers for heavy computation
|
|
- Optimize third-party scripts
|
|
|
|
### Issue: Slow TTFB (>1s)
|
|
**Causes:**
|
|
- Slow server response
|
|
- No CDN
|
|
- Database queries during render
|
|
|
|
**Fixes:**
|
|
- Enable Cloudflare CDN
|
|
- Use ISR instead of SSR
|
|
- Move database queries to edge (D1)
|
|
- Implement caching layers
|
|
|
|
## Monitoring Dashboard Setup
|
|
|
|
### Cloudflare Web Analytics
|
|
```html
|
|
<!-- Add to app/layout.tsx <head> -->
|
|
<script defer src='https://static.cloudflareinsights.com/beacon.min.js'
|
|
data-cf-beacon='{"token": "YOUR_TOKEN"}'></script>
|
|
```
|
|
|
|
### Custom Performance Monitoring
|
|
```typescript
|
|
// app/layout.tsx
|
|
'use client';
|
|
|
|
import { useEffect } from 'react';
|
|
import { usePathname } from 'next/navigation';
|
|
|
|
export function PerformanceMonitor() {
|
|
const pathname = usePathname();
|
|
|
|
useEffect(() => {
|
|
// Measure Web Vitals
|
|
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
|
getCLS(console.log);
|
|
getFID(console.log);
|
|
getFCP(console.log);
|
|
getLCP(console.log);
|
|
getTTFB(console.log);
|
|
});
|
|
}, [pathname]);
|
|
|
|
return null;
|
|
}
|
|
```
|
|
|
|
## Resources
|
|
|
|
- [Next.js Performance Docs](https://nextjs.org/docs/app/building-your-application/optimizing)
|
|
- [Web.dev Core Web Vitals](https://web.dev/vitals/)
|
|
- [Lighthouse CI](https://github.com/GoogleChrome/lighthouse-ci)
|
|
- [Cloudflare Performance](https://developers.cloudflare.com/fundamentals/speed/)
|