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>
17 KiB
17 KiB
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)
// 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)
<script defer src='https://static.cloudflareinsights.com/beacon.min.js'
data-cf-beacon='{"token": "YOUR_TOKEN"}'></script>
2. Lab Testing
# Lighthouse (local)
npx lighthouse https://mylder.io --view
# WebPageTest (global)
# https://www.webpagetest.org
# Chrome DevTools
# DevTools → Performance → Record → Analyze
3. Continuous Monitoring
# 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
// 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
# 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
// 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
// Automatic with App Router
app/
dashboard/ # Separate chunk
settings/ # Separate chunk
(marketing)/ # Separate chunk (route group)
Vendor Splitting
// next.config.ts
experimental: {
optimizePackageImports: [
'@radix-ui/react-icons',
'lucide-react',
'date-fns',
],
},
4. Server Components (Default)
// 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)
// 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
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
- AVIF (best compression, 50% smaller than JPEG)
- WebP (wide support, 30% smaller than JPEG)
- JPEG/PNG (fallback)
3. Cloudflare Image Optimization
// 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
// 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
priorityfor 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)
// 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
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
// 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
// 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
// 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
# 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
// 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)
// 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)
// 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)
// 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
// 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
// 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
// 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)
// n8n workflow: CMS Update → POST /api/revalidate
{
"path": "/blog",
"tag": "posts"
}
3. Cloudflare Cache Purge
# 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
# 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
# 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
# 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
# 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
reactStrictModeandswcMinify - 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
priorityfor 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
<!-- 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
// 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;
}