Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Modern frontend patterns for React Server Components, performance optimization, and Core Web Vitals
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-07 | ✓→✓ | = Same ✓ | 31% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 64% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 51% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 42% | 0% |
Server Components run on the server and send rendered HTML to the client. They can directly access databases, filesystems, and internal APIs without exposing them to the browser.
tsx// app/products/page.tsx (Server Component by default) async function ProductsPage() { const products = await db.query("SELECT * FROM products WHERE active = true"); return ( <main> <h1>Products</h1> <ProductList products={products} /> <AddToCartButton /> {/* Client Component */} </main> ); }
Rules:
useState, useEffect, or browser APIs'use client' at the top of the file'use client' boundary as deep in the tree as possibletsximport { Suspense } from 'react'; export default function Dashboard() { return ( <div> <Header /> {/* renders immediately */} <Suspense fallback={<ChartSkeleton />}> <AnalyticsChart /> {/* streams when ready */} </Suspense> <Suspense fallback={<TableSkeleton />}> <RecentOrders /> {/* streams independently */} </Suspense> </div> ); }
Each Suspense boundary streams independently. Place boundaries around data-fetching components to avoid blocking the entire page.
tsximport dynamic from 'next/dynamic'; const HeavyEditor = dynamic(() => import('@/components/Editor'), { loading: () => <EditorSkeleton />, ssr: false, }); const AdminPanel = dynamic(() => import('@/components/AdminPanel'));
Split on:
javascript// next.config.js module.exports = { experimental: { optimizePackageImports: ['lucide-react', '@heroicons/react', 'lodash-es'], }, };
Checklist:
npx next build and review the output size per route@next/bundle-analyzer to identify large dependenciesmoment with date-fns or dayjs (save ~200KB)import { debounce } from 'lodash-es/debounce'import { Search } from 'lucide-react'| Metric | Good | Needs Work | Poor | |--------|------|------------|------| | LCP (Largest Contentful Paint) | <2.5s | 2.5-4.0s | >4.0s | | INP (Interaction to Next Paint) | <200ms | 200-500ms | >500ms | | CLS (Cumulative Layout Shift) | <0.1 | 0.1-0.25 | >0.25 |
<link rel="preload" as="image" href="..." />priority prop on above-the-fold <Image> componentswidth/height on images to prevent layout shiftstsximport Image from 'next/image'; <Image src="/hero.jpg" alt="Descriptive alt text" width={1200} height={630} priority // preload for LCP images sizes="(max-width: 768px) 100vw, 50vw" placeholder="blur" blurDataURL={base64} // inline tiny placeholder />
next/image or equivalent (automatic WebP/AVIF, responsive srcset)sizes attribute to avoid downloading oversized imagesplaceholder="blur" with a base64 data URL for perceived performancetsx// app/layout.tsx import { Inter } from 'next/font/google'; const inter = Inter({ subsets: ['latin'], display: 'swap', // show fallback font immediately preload: true, variable: '--font-inter', }); export default function RootLayout({ children }) { return ( <html className={inter.variable}> <body>{children}</body> </html> ); }
next/font for zero-CLS font loading with automatic subsettingdisplay: 'swap' to avoid invisible text during loadwidth and height on images and videosaspect-ratio CSS for responsive media containersmin-heightcontain: layout for components that change sizetypescriptimport { onCLS, onINP, onLCP } from 'web-vitals'; onCLS(console.log); onINP(console.log); onLCP(console.log);
Measure real user metrics (RUM), not just lab scores. Vercel Analytics and Google Search Console provide field data.
Other measured skills in the registry, with their headline benchmark lift.