Semua Blog
    Development7 menit baca

    Cara Meningkatkan Performa Website dengan React dan Next.js

    Panduan praktis membuat website React dan Next.js lebih cepat: ukur Core Web Vitals, kirim lebih sedikit JavaScript dengan Server Components, pre-render dengan SSG, dan optimalkan gambar, font, serta script pihak ketiga.

    Performa adalah fitur. Halaman yang lambat kehilangan pengunjung sebelum mereka sempat melihat karyamu, dan sejak Core Web Vitals menjadi sinyal peringkat Google, halaman lambat juga mengurangi trafik dari mesin pencari. Kabar baiknya, React dan Next.js sudah menyediakan hampir semua alat yang kamu butuhkan. Kamu hanya perlu memakainya dengan sengaja.

    Belum lama ini saya membangun ulang portfolio ini dari aplikasi Vite yang di-render di sisi klien menjadi Next.js. Berikut teknik-teknik yang paling berpengaruh, lengkap dengan kode yang bisa langsung kamu pakai di proyekmu sendiri.

    1. Ukur dulu sebelum mengoptimalkan

    Menebak-nebak adalah cara tercepat untuk membuang waktu seharian. Mulailah dari tiga metrik Core Web Vitals yang dipakai Google untuk menilai pengalaman pengguna sungguhan:

    • LCP (Largest Contentful Paint): seberapa cepat konten utama muncul. Targetkan 2,5 detik atau kurang.
    • INP (Interaction to Next Paint): seberapa cepat halaman merespons klik dan ketikan. Targetkan 200 ms atau kurang.
    • CLS (Cumulative Layout Shift): seberapa banyak tata letak bergeser saat halaman dimuat. Targetkan 0,1 atau kurang.

    Gunakan Lighthouse atau PageSpeed Insights untuk data lab, lalu pastikan dengan data lapangan (field data) dari pengguna sungguhan. Next.js menyediakan metrik ini lewat useReportWebVitals, jadi kamu bisa mengirimkannya ke tool analytics apa pun:

    app/web-vitals.tsxtsx
    "use client";
    
    import { useReportWebVitals } from "next/web-vitals";
    
    export function WebVitals() {
      useReportWebVitals((metric) => {
        // Replace with your analytics endpoint
        console.log(metric.name, Math.round(metric.value));
      });
    
      return null;
    }

    2. Render di server dan kirim lebih sedikit JavaScript

    Di App Router, setiap komponen secara default adalah React Server Component. Kodenya dijalankan di server dan tidak pernah dikirim ke browser, sehingga tidak menambah satu byte pun ke bundle JavaScript. Hanya komponen yang ditandai "use client" yang dikirim ke browser lalu di-hydrate.

    Aturan praktisnya: letakkan "use client" sedalam mungkin di pohon komponen. Biarkan page dan layout tetap di server, dan jadikan Client Component hanya bagian-bagian kecil yang interaktif.

    app/blogs/[slug]/page.tsxtsx
    // Server Component: none of this code is sent to the browser
    import { LikeButton } from "./like-button"; // the only client code on this page
    
    export default async function PostPage({ params }) {
      const { slug } = await params;
      const post = await getPost(slug);
    
      return (
        <article>
          <h1>{post.title}</h1>
          <PostBody content={post.content} />
          <LikeButton slug={slug} />
        </article>
      );
    }
    JavaScript tercepat adalah JavaScript yang tidak pernah kamu kirim ke browser.

    3. Pre-render halaman dengan SSG dan ISR

    Kalau sebuah halaman terlihat sama untuk semua pengunjung, tidak ada alasan untuk me-render-nya di setiap request. Static Site Generation (SSG) membuat HTML sekali saat build, lalu menyajikannya langsung dari CDN. Ini hampir secepat yang bisa dicapai di web, dan artikel ini pun di-render dengan cara itu.

    app/blogs/[slug]/page.tsxtsx
    // Build one static HTML page per post at build time
    export const generateStaticParams = () =>
      blogs.map((post) => ({ slug: post.slug }));
    
    // Any slug not returned above becomes a 404 instead of a server render
    export const dynamicParams = false;

    Kalau kontennya berubah secara berkala, gunakan Incremental Static Regeneration (ISR). Halaman tetap statis, dan Next.js membangunnya ulang di latar belakang paling sering sekali per interval:

    tsx
    // Regenerate this page at most once an hour
    export const revalidate = 3600;

    Simpan dynamic rendering untuk halaman yang benar-benar bergantung pada request, seperti dashboard, keranjang belanja, atau apa pun yang membaca cookie.

    4. Optimalkan gambar dengan next/image

    Gambar biasanya bagian terberat dari sebuah halaman dan paling sering menjadi elemen LCP. Di situs ini, logo berukuran 40 px di navbar dulunya dikirim sebagai JPEG berukuran 2481 px dan 167 KB. Beralih ke next/image menyelesaikannya hanya dengan satu baris.

    • Gambar diubah ukurannya sesuai ukuran tampilannya dan dikirim dalam format modern seperti WebP dan AVIF.
    • Gambar di bawah layar pertama otomatis di-lazy-load.
    • Ruang untuk gambar sudah disiapkan berdasarkan lebar dan tingginya, sehingga tidak terjadi layout shift (CLS).
    tsx
    import Image from "next/image";
    import hero from "@/assets/images/hero.webp";
    
    export function Hero() {
      return (
        <Image
          src={hero}
          alt="Dashboard preview"
          sizes="(min-width: 1024px) 50vw, 100vw"
          placeholder="blur"
          preload // the LCP image: load it early (called "priority" before Next.js 16)
        />
      );
    }

    Selalu isi atribut sizes untuk gambar responsif. Tanpanya, browser bisa mengunduh file yang jauh lebih besar dari yang dibutuhkan tata letak.

    5. Muat font dengan next/font

    Memuat Google Fonts lewat @import di CSS akan menahan proses render: browser harus mengambil stylesheet-nya dulu, lalu file font-nya, baru bisa menampilkan teks. next/font mengunduh font saat build, menyajikannya dari domain kamu sendiri, dan membuat fallback dengan ukuran yang disesuaikan supaya teks tidak melompat saat font aslinya selesai dimuat.

    app/layout.tsxtsx
    import { Syne } from "next/font/google";
    
    const syne = Syne({
      subsets: ["latin"],
      display: "swap",
      variable: "--font-syne",
    });
    
    export default function RootLayout({ children }) {
      return (
        <html lang="en" className={syne.variable}>
          <body>{children}</body>
        </html>
      );
    }

    6. Pecah komponen berat dengan next/dynamic

    Chart, peta, rich text editor, dan video player masing-masing bisa menambah ratusan kilobyte. Kalau komponen itu tidak dibutuhkan untuk tampilan pertama, muat hanya saat komponen tersebut di-render:

    tsx
    "use client";
    
    import dynamic from "next/dynamic";
    
    const RevenueChart = dynamic(() => import("./revenue-chart"), {
      ssr: false,
      loading: () => <div className="h-80 animate-pulse rounded-2xl bg-secondary" />,
    });

    Buat placeholder loading setinggi komponen aslinya. Kalau tidak, kamu hanya menukar bundle yang lebih kecil dengan layout shift.

    7. Jaga interaksi tetap responsif

    Muatan awal yang cepat tidak banyak artinya kalau halaman macet saat seseorang mengetik. Itulah yang diukur oleh INP. React menyediakan dua alat untuk mendahulukan update yang mendesak (mengetik, mengklik) di atas update yang berat (memfilter daftar panjang):

    • useDeferredValue membuat input langsung ter-update, sementara hasil yang berat menyusul belakangan.
    • useTransition menandai sebuah state update sebagai tidak mendesak, sehingga React bisa menundanya demi input pengguna.
    tsx
    "use client";
    
    import { useDeferredValue, useState } from "react";
    
    export function ProjectSearch({ projects }) {
      const [query, setQuery] = useState("");
      const deferredQuery = useDeferredValue(query);
    
      const results = projects.filter((project) =>
        project.title.toLowerCase().includes(deferredQuery.toLowerCase())
      );
    
      return (
        <>
          <input value={query} onChange={(e) => setQuery(e.target.value)} />
          <ProjectList items={results} />
        </>
      );
    }

    Penundaan ini hanya membantu kalau ProjectList tidak re-render ketika props-nya tidak berubah. Daripada menaburkan memo dan useMemo di mana-mana, aktifkan React Compiler yang menambahkan memoization itu secara otomatis. Di Next.js 16, install babel-plugin-react-compiler lalu set reactCompiler: true di next.config.ts.

    8. Kendalikan script pihak ketiga

    Widget chat, tag manager, dan tracking pixel sering menjadi biang lambatnya halaman karena berjalan di main thread saat halaman sedang dimuat. Muat semuanya dengan next/script dan pilih strategy yang sesuai dengan seberapa penting script tersebut:

    tsx
    import Script from "next/script";
    
    // Not needed for the first paint: load it once the browser is idle
    <Script src="https://widget.example.com/chat.js" strategy="lazyOnload" />

    Sebelum menambahkan script apa pun, tanyakan dulu apakah kamu benar-benar membutuhkannya. Script pihak ketiga yang paling murah adalah script yang kamu hapus.

    9. Ambil data secara paralel dan simpan di cache

    Pemanggilan await secara berurutan menciptakan waterfall: setiap request menunggu request sebelumnya selesai. Kalau request-request itu tidak saling bergantung, jalankan bersamaan:

    tsx
    // Slow: the second request waits for the first
    const profile = await getProfile();
    const projects = await getProjects();
    
    // Fast: both requests run at the same time
    const [profile, projects] = await Promise.all([getProfile(), getProjects()]);

    Lalu simpan di cache data yang tidak berubah di setiap request. Dengan fetch di Server Component, satu opsi saja sudah cukup untuk menyimpan response di cache dan memperbaruinya di latar belakang:

    tsx
    const res = await fetch("https://api.example.com/projects", {
      next: { revalidate: 3600 }, // reuse the response for up to an hour
    });

    Checklist singkat

    1. Ukur LCP, INP, dan CLS dengan data pengguna sungguhan sebelum mengubah apa pun.
    2. Biarkan komponen tetap di server dan tambahkan "use client" hanya di bagian yang butuh interaktivitas.
    3. Pre-render halaman statis dengan generateStaticParams, dan gunakan revalidate untuk konten yang berubah.
    4. Sajikan semua gambar lewat next/image dengan sizes yang benar, dan preload gambar LCP.
    5. Muat font dengan next/font, bukan lewat import CSS.
    6. Lazy-load komponen berat yang berada di bawah layar pertama dengan next/dynamic.
    7. Gunakan useDeferredValue, useTransition, dan React Compiler agar interaksi tetap cepat.
    8. Muat script pihak ketiga dengan next/script, atau hapus saja.
    9. Jalankan request data yang tidak saling bergantung secara paralel, lalu simpan hasilnya di cache.

    Kamu tidak perlu melakukan semuanya sekaligus. Pilih metrik yang paling buruk di situsmu, perbaiki penyebab terbesarnya, ukur lagi, lalu ulangi. Perbaikan kecil yang terukur akan terkumpul menjadi situs yang terasa instan.

    ReactNext.jsPerformanceCore Web VitalsSSG
    Kembali ke Blog