Last Updated: 2026-03-15 | Version: 1.5.0
| Metric | Target | Tool |
|---|---|---|
| First Contentful Paint | < 1.5s | Vercel Analytics |
| Largest Contentful Paint | < 2.5s | Core Web Vitals |
| API Response Time (p95) | < 300ms | Sentry Performance |
| Dashboard Load | < 2s | Browser DevTools |
Use RSC wherever possible to reduce client-side JavaScript:
// Server Component - no JS sent to client!
async function StatsWidget({ userId }: { userId: string }) {
const stats = await prisma.user.findUnique({
where: { id: userId },
select: { totalProblems: true, currentStreak: true }
});
return <div>{stats.totalProblems}</div>; // Rendered server-side
}// ❌ N+1 query - one query per goal
const goals = await prisma.goal.findMany({ where: { userId } });
for (const goal of goals) {
const platform = await prisma.platform.findUnique({ where: { id: goal.platformId } });
}
// ✅ Single query with join
const goals = await prisma.goal.findMany({
where: { userId },
include: { platform: true } // JOIN in one query
});// ❌ Fetches entire User model (many columns)
const user = await prisma.user.findUnique({ where: { id } });
// ✅ Fetch only needed fields
const user = await prisma.user.findUnique({
where: { id },
select: { name: true, email: true, currentStreak: true }
});// next.config.ts
const config: NextConfig = {
// Analyze bundle
webpack: (config, { isServer }) => {
if (!isServer) {
// Dynamic imports for heavy libraries
config.optimization.splitChunks = { chunks: 'all' };
}
return config;
}
};// Load charts only when needed
const ActivityHeatmap = dynamic(() => import('./ActivityHeatmap'), {
loading: () => <Skeleton className="h-40" />,
ssr: false, // Charts need window object
});// Use Next.js Image component
import Image from 'next/image';
<Image
src={user.image}
width={40}
height={40}
alt={user.name}
// Automatically:
// - WebP format
// - Lazy loading
// - Appropriate srcset
/>