Quick reference untuk optimasi performa website. Core Web Vitals, lazy loading, code splitting, dan teknik optimization lainnya.
Metrik-metrik penting yang diukur Google untuk menilai pengalaman pengguna pada website.
Waktu yang dibutuhkan untuk memuat konten terbesar yang terlihat di viewport.
// Target: < 2.5s
// Mengukur: Loading performance (konten terbesar)
// Monitor LCP
import { onLCP } from 'web-vitals';
onLCP((metric) =>
Optimization:
Waktu responsifitas halaman terhadap semua interaksi pengguna.
// Target: < 200ms
// Mengukur: Responsiveness (semua interaksi)
// Monitor INP
import { onINP } from 'web-vitals';
onINP((metric) => {
console.log('INP:', metric.value);
});Optimization:
Mengukur stabilitas visual halaman selama loading.
// Target: < 0.1
// Mengukur: Visual stability
// Monitor CLS
import { onCLS } from 'web-vitals';
onCLS((metric) => {
console.log('CLS:', metric.value);
});Optimization:
Teknik-teknik untuk mengoptimalkan gambar agar lebih cepat dimuat.
Memuat gambar hanya saat akan terlihat di viewport.
<!-- Basic lazy loading -->
<img
src="photo.jpg"
alt="Description"
loading="lazy"
width="800"
height="600"
/>
<!-- Lazy iframe -->
<iframe
src="video.html"
<!-- srcset untuk different sizes -->
<img
srcset="
small.webp 400w,
medium.webp 800w,
large.webp 1200w
"
sizes="(max-width: 600px) 400px,
(max-width: 900px) 800px,
1200px"
src="medium.webp"
alt="Responsive image"
loading
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Modern format fallback"
import Image from 'next/image';
<Image
src="/photo.jpg"
alt="Photo"
width={800}
height={600}
loading="lazy"
placeholder
// Load module on demand
button.addEventListener('click', async () => {
const module = await import('./heavyModule.js');
module.initialize();
});import { lazy, Suspense } from 'react';
// Lazy load component
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback=
import dynamic from 'next/dynamic';
// Without SSR
const DynamicComponent = dynamic(
() => import('../components/heavy'),
{ ssr: false }
);
// With loading state
const DynamicWithLoading
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
<head>
<!-- Preload font -->
<link
rel="preload"
href="/fonts/inter.woff2"
as="font"
type="font/woff2"
crossorigin
/>
</head>@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap; /* Show fallback immediately */
font-weight: 400;
font-style: normal
import { Inter, Roboto_Mono } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
});
const robotoMono = Roboto_Mono({
subsets: [
<head>
<!-- Inline critical CSS -->
<style>
/* Above-the-fold styles */
body { margin: 0; font-family: sans-serif; }
.hero { min-height: 100vh; }
// PurgeCSS config (Tailwind CSS)
module.exports = {
content: [
'./pages/**/*.{js,jsx,ts,tsx}',
'./components/**/*.{js,jsx,ts,tsx}',
],
// PurgeCSS akan remove unused styles
};<!-- Preconnect ke external domains -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://cdn.example.com"<!-- DNS prefetch untuk less critical domains -->
<link rel="dns-prefetch" href="https://analytics.example.com">
<link rel="dns-prefetch" href="https://ads.example.com"><!-- Preload critical assets -->
<link rel="preload" href="/hero.webp" as="image">
<link rel="preload" href="/critical.css" as="style">
<link rel=<!-- Prefetch pages yang mungkin dikunjungi -->
<link rel="prefetch" href="/about">
<link rel="prefetch" href="/products"><!-- async: Load parallel, execute ASAP -->
<script src="analytics.js" async></script>
<!-- defer: Load parallel, execute after HTML parsed -->
<script src="app.js" defer></script>
<!-- blocking: Avoid kalo bisa -->
<script src=import Script from 'next/script';
export default function Page() {
return (
<>
{/* Load after page interactive */}
<Script
src="https://analytics.example.com/script.js"
import { useMemo } from 'react';
function ExpensiveComponent({ data }) {
// Memoize expensive calculation
const processedData = useMemo(() => {
return data.map(item => heavyComputation(item));
import { useCallback } from 'react';
function Parent() {
// Memoize function
const handleClick = useCallback(() => {
console.log('Clicked');
}, []); // Function never changes
return <
import { memo } from 'react';
// Prevent unnecessary re-renders
const ExpensiveChild = memo(({ data }) => {
return <div>{data}</div>;
});
// Custom comparison
const
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index]}
# nginx.conf
http {
# Gzip compression
gzip on;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
# Brotli compression (better than gzip)
brotli on;
brotli_types text/plain text/css application/json application/javascript text/xml application/xml;
// next.config.js
module.exports = {
compress: true, // Enable gzip compression
};// Next.js API route
export default function handler(req, res) {
res.setHeader(
'Cache-Control',
'public, s-maxage=31536000, immutable'
);
res.json({ data: 'cached data' });
}// service-worker.js
const CACHE_NAME = 'v1';
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
// Send to your analytics service
fetch('/analytics', {
method: 'POST',
body: JSON.stringify(metric),
// Measure custom timing
performance.mark('start-task');
// ... do something ...
performance.mark('end-task');
performance.measure('task-duration', 'start-task', 'end-task');
// Get measurements
const
Free:
Paid:
| Metric | Good | Needs Improvement | Poor |
|---|---|---|---|
| LCP | < 2.5s | 2.5s - 4s | > 4s |
| INP | < 200ms | 200ms - 500ms | > 500ms |
| CLS | < 0.1 | 0.1 - 0.25 | > 0.25 |
| FCP | < 1.8s | 1.8s - 3s | > 3s |
| TTFB | < 800ms | 800ms - 1.8s | > 1.8s |