29. Asset Optimization

Pemuatan aset yang efisien—seperti gambar, font, video, dan script pihak ketiga—sangat penting untuk menjaga kinerja aplikasi web tetap optimal. Aset yang tidak dioptimalkan dapat memperlambat rendering halaman, menurunkan skor Core Web Vitals (khususnya LCP dan CLS), serta mengganggu kenyamanan pengguna (User Experience).

Dokumentasi ini menjelaskan standar industri dan praktik terbaik untuk melakukan optimasi aset pada aplikasi React dan Next.js (App Router).


A. Perbandingan Format Gambar & Kapan Menggunakannya

Memilih format gambar yang tepat adalah langkah pertama dalam optimasi. Gunakan tabel berikut sebagai panduan:

FormatTingkat KompresiTransparansiAnimasiKasus Penggunaan Utama
AVIFSangat Tinggi (30% lebih kecil dari WebP)YaYaPilihan utama untuk semua foto/gambar ilustrasi kompleks jika didukung browser modern.
WebPTinggi (26% lebih kecil dari PNG/JPEG)YaYaFormat fallback universal untuk semua gambar grafis, foto, dan ilustrasi di web.
SVGBerbasis Vektor (Sangat Kecil)YaYaIkon, logo, ilustrasi geometris sederhana yang perlu diskalakan tanpa pecah.
PNGTanpa Kehilangan (Lossless) - BesarYaTidakHanya digunakan jika membutuhkan detail piksel transparan yang sangat presisi/kasus khusus.
JPEGKehilangan (Lossy) - SedangTidakTidakFormat warisan (legacy). Hindari, konversikan ke WebP/AVIF.

B. Optimasi Gambar di Next.js (<Image />)

Komponen <Image /> bawaan Next.js memperluas tag <img> HTML standar dengan fitur optimasi otomatis terintegrasi. Hindari penggunaan tag <img> standar kecuali untuk kasus yang sangat khusus.

Fitur Utama Next.js <Image />:

  1. Optimasi Ukuran secara Dinamis: Menyajikan gambar dengan dimensi yang tepat sesuai perangkat pengguna.
  2. Pencegahan Layout Shift (Zero CLS): Mengharuskan penentuan width & height atau penggunaan properti fill agar browser memesan ruang gambar sebelum dimuat.
  3. Lazy Loading Otomatis: Gambar di luar area pandang (viewport) hanya akan dimuat saat pengguna menggulir mendekatinya.
  4. Dukungan Format Modern: Next.js secara otomatis mendeteksi dukungan browser dan mengirimkan format WebP atau AVIF jika didukung.

1. Implementasi Gambar Lokal (Static Import)

Untuk gambar lokal, Next.js secara otomatis mendeteksi lebar dan tinggi gambar saat build time. Fitur ini juga mendukung efek placeholder blur otomatis.

import Image from 'next/image';
import heroBanner from '@/public/images/hero-banner.png'; // Import statis

export default function HeroSection() {
  return (
    <div className="relative w-full">
      <Image
        src={heroBanner}
        alt="Banner Utama Aplikasi"
        placeholder="blur" // Menampilkan efek blur saat memuat
        priority // Diprioritaskan untuk LCP (Above-the-Fold)
        className="rounded-lg object-cover"
      />
    </div>
  );
}

2. Implementasi Gambar Dinamis / Remote (Menggunakan fill)

Jika gambar berasal dari URL eksternal (API atau CMS) dan ukurannya tidak diketahui secara statis, gunakan properti fill bersamaan dengan container induk yang memiliki posisi relative, absolute, atau fixed.

import Image from 'next/image';

interface ProductCardProps {
  imageUrl: string;
  title: string;
}

export default function ProductCard({ imageUrl, title }: ProductCardProps) {
  return (
    <div className="border rounded-lg overflow-hidden bg-white">
      {/* Container induk wajib memiliki class relative dan tinggi yang pasti */}
      <div className="relative w-full h-64 bg-gray-100">
        <Image
          src={imageUrl}
          alt={title}
          fill // Memenuhi container induk
          sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" // Responsivitas browser
          loading="lazy" // Default behavior, dimuat secara asinkronus
          className="object-cover transition-transform hover:scale-105 duration-300"
        />
      </div>
      <div className="p-4">
        <h3 className="font-semibold text-lg">{title}</h3>
      </div>
    </div>
  );
}

[!IMPORTANT]

  • Atribut sizes: Selalu tentukan atribut sizes saat menggunakan fill. Ini memberi tahu browser berapa lebar gambar yang harus diunduh berdasarkan ukuran viewport, mencegah pengunduhan gambar beresolusi terlalu besar pada perangkat mobile.
  • Konfigurasi Domain Remote: Untuk memuat gambar dari domain luar, Anda wajib mendaftarkannya di berkas next.config.mjs demi alasan keamanan:
    const nextConfig = {
      images: {
        remotePatterns: [
          {
            protocol: 'https',
            hostname: 'images.unsplash.com',
            pathname: '/**',
          },
        ],
      },
    };
    

C. Optimasi Web Fonts

Font yang tidak dioptimalkan dapat memicu masalah Flash of Unstyled Text (FOUT) (teks default muncul sebentar sebelum berganti ke font custom) atau Flash of Invisible Text (FOIT) (teks tidak terlihat sama sekali sampai font selesai diunduh). Keduanya berdampak buruk pada metrik CLS dan kenyamanan membaca.

1. Menggunakan Google Fonts dengan next/font/google

Next.js menyediakan modul next/font yang secara otomatis mengunduh berkas font saat build time dan menghostingnya secara lokal bersama dengan aset aplikasi lainnya (zero layout shift, tanpa request HTTP ke server Google Font).

// src/app/layout.tsx
import { Inter, Outfit } from 'next/font/google';
import './globals.css';

// Konfigurasi font utama
const inter = Inter({
  subsets: ['latin'],
  display: 'swap', // Mencegah FOIT dengan fallback sistem instan
  variable: '--font-inter', // Opsional: definisikan CSS variable
});

const outfit = Outfit({
  subsets: ['latin'],
  weight: ['400', '600', '700'],
  display: 'swap',
  variable: '--font-outfit',
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="id" className={`${inter.variable} ${outfit.variable}`}>
      <body className="font-sans antialiased">
        {children}
      </body>
    </html>
  );
}

Di Tailwind CSS, Anda bisa mendaftarkan CSS variable ini di file tailwind.config.js:

module.exports = {
  theme: {
    extend: {
      fontFamily: {
        sans: ['var(--font-inter)', 'sans-serif'],
        display: ['var(--font-outfit)', 'serif'],
      },
    },
  },
};

2. Menggunakan Custom Local Fonts (next/font/local)

Jika Anda memiliki berkas font custom sendiri (misal format .woff2), gunakan next/font/local:

import localFont from 'next/font/local';

const myCustomFont = localFont({
  src: [
    {
      path: '../../public/fonts/CustomFont-Regular.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: '../../public/fonts/CustomFont-Bold.woff2',
      weight: '700',
      style: 'normal',
    },
  ],
  display: 'swap',
  variable: '--font-custom',
});

D. Optimasi Video & Media Berat

Berkas video memiliki ukuran yang jauh lebih besar daripada gambar. Untuk menjaga kecepatan halaman:

  1. Gunakan Format Modern: Selalu sediakan format .webm (kompresi modern berkualitas tinggi) dan tawarkan .mp4 sebagai fallback.
  2. Gunakan Atribut Pemuatan yang Tepat:
    • preload="none": Bagus untuk video di bawah lipatan halaman (below-the-fold) agar tidak membuang bandwidth sebelum video diputar.
    • preload="metadata": Bagus jika Anda ingin menampilkan durasi/thumbnail awal tanpa mengunduh seluruh video.
    • playsinline: Wajib untuk perangkat iOS agar video dapat diputar langsung di dalam halaman, tidak masuk ke mode fullscreen bawaan safari.
  3. Lazy Loading Video: Bungkus elemen video dalam wrapper lazy loading yang hanya me-render tag video saat mendekati viewport.

Contoh Tag Video yang Dioptimalkan:

export default function OptimizedVideo() {
  return (
    <video
      controls
      preload="metadata"
      poster="/images/video-thumbnail.jpg" // Gambar penutup sebelum video diputar
      playsInline
      className="w-full rounded-xl shadow-lg"
    >
      <source src="/videos/product-demo.webm" type="video/webm" />
      <source src="/videos/product-demo.mp4" type="video/mp4" />
      Browser Anda tidak mendukung tag video HTML5.
    </video>
  );
}

E. Optimasi Third-Party Scripts

Script pihak ketiga (seperti Google Analytics, GTM, Facebook Pixel, Chat Widget) adalah salah satu penyebab utama lambatnya skor interaktivitas (FID / INP). Komponen <Script /> dari Next.js memberikan kontrol penuh terhadap bagaimana script tersebut dimuat.

Strategi Pemuatan (strategy):

StrategiKapan DigunakanCara Kerja
beforeInteractiveSangat jarang. Script penting sebelum halaman interaktif (misal: deteksi bot).Dimuat di server dan disuntikkan ke HTML sebelum file JS Next.js utama dijalankan.
afterInteractiveDefault. Script pelacakan, analytics dasar, tag managers.Dimuat di browser segera setelah halaman selesai mengalami hydration.
lazyOnloadScript non-esensial (misal: widget Chat bantuan, widget survei).Dimuat di browser selama waktu senggang (idle time) setelah semua aset utama selesai.
worker(Eksperimental) Script pelacakan yang intens.Menjalankan script di dalam Web Worker agar tidak memblokir Main Thread utama.

Contoh Implementasi Script Pihak Ketiga:

import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html lang="id">
      <body>
        {children}
        
        {/* Google Analytics - Dimuat setelah interaktif */}
        <Script
          src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
          strategy="afterInteractive"
        />
        <Script id="google-analytics" strategy="afterInteractive">
          {`
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', 'GA_MEASUREMENT_ID');
          `}
        </Script>

        {/* Live Chat Widget - Dimuat di waktu idle */}
        <Script
          src="https://example.com/widget/chat.js"
          strategy="lazyOnload"
        />
      </body>
    </html>
  );
}

F. Alur Keputusan Optimasi Aset (Decision Tree)

Gunakan bagan keputusan berikut untuk menentukan perlakuan optimasi pada setiap aset aplikasi Anda:

graph TD
    A[Mulai Optimasi Aset] --> B{Jenis Aset?}
    
    B -- Gambar --> C{Format Gambar?}
    C -- Vektor/Ikon/Logo --> D[Gunakan format SVG]
    C -- Foto/Ilustrasi Kompleks --> E{Apakah lokasinya static/local?}
    E -- Ya --> F[Gunakan Image Next.js + static import + placeholder blur]
    E -- Tidak --> G[Gunakan Image Next.js + fill + sizes + lazy load + priority jika LCP]
    
    B -- Font --> H{Sumber Font?}
    H -- Google Fonts --> I[Gunakan next/font/google + display swap]
    H -- Custom File --> J[Gunakan next/font/local + format .woff2]
    
    B -- Video --> K{Durasi & Tujuan?}
    K -- Dekorasi/Loop Singkat --> L[Konversi ke WebM + tag video muted/loop/playsinline]
    K -- Durasi Panjang/Demo --> M[Gunakan poster + preload metadata / pertimbangkan streaming HLS]
    
    B -- Script Ketiga --> N{Apakah Kritikal untuk Tampilan Awal?}
    N -- Ya/Analytics dasar --> O[Gunakan Script Next.js + strategy afterInteractive]
    N -- Tidak/Chat/Survei --> P[Gunakan Script Next.js + strategy lazyOnload]

G. Referensi Tambahan

© 2026 Bit Group Asia. All rights reserved.

ArchitectureSecurityGuidelines