Langsung ke konten
KamusNgoding
Menengah Nextjs 7 menit baca

SSR vs SSG di Next.js: Perbedaan dan Kapan Menggunakannya

#nextjs #ssr #ssg #rendering #webdev

SSR vs SSG di Next.js: Perbedaan dan Kapan Menggunakannya

Pendahuluan

Salah satu keputusan paling krusial saat membangun aplikasi dengan Next.js adalah memilih strategi rendering yang tepat. Apakah halaman harus di-render di server setiap kali ada request? Atau cukup di-generate sekali saat build dan disajikan sebagai file statis?

Di sinilah SSR (Server-Side Rendering) dan SSG (Static Site Generation) memainkan perannya. Keduanya bisa menghasilkan HTML yang siap dibaca mesin pencari, tetapi cara kerjanya — dan kapan kamu harus memilih masing-masing — sangat berbeda.

Artikel ini akan membedah perbedaan keduanya secara mendalam, lengkap dengan contoh kode nyata dan panduan memilih strategi yang paling sesuai untuk proyekmu.


Memahami Server-Side Rendering (SSR) di Next.js

SSR berarti setiap kali pengguna membuka halaman, server akan memproses request tersebut, mengambil data terbaru, lalu mengembalikan HTML yang sudah terisi ke browser. Proses ini terjadi on-demand — setiap request, server bekerja.

Di Next.js (App Router), SSR diaktifkan dengan menggunakan async component dan melakukan fetch data di dalam component itu sendiri tanpa opsi cache:

// app/products/page.tsx
export default async function ProductsPage() {
  // Fetch dilakukan setiap request (SSR)
  const res = await fetch('https://api.toko.com/products', {
    cache: 'no-store', // kunci utama SSR
  });
  const products = await res.json();

  return (
    <main>
      <h1>Daftar Produk Terbaru</h1>
      <ul>
        {products.map((product: { id: number; name: string; price: number }) => (
          <li key={product.id}>
            {product.name} — Rp {product.price.toLocaleString('id-ID')}
          </li>
        ))}
      </ul>
    </main>
  );
}

Di Pages Router (cara lama), SSR diimplementasikan dengan getServerSideProps:

// pages/products.tsx
import type { GetServerSideProps } from 'next';

type Product = { id: number; name: string; price: number };

export const getServerSideProps: GetServerSideProps = async (context) => {
  const res = await fetch('https://api.toko.com/products');
  const products: Product[] = await res.json();

  return {
    props: { products },
  };
};

export default function ProductsPage({ products }: { products: Product[] }) {
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>{p.name} — Rp {p.price.toLocaleString('id-ID')}</li>
      ))}
    </ul>
  );
}

Kapan menggunakan SSR?

  • Data berubah sangat sering (harga saham, stok produk real-time)
  • Konten bersifat personal (dashboard pengguna, feed berdasarkan login)
  • Data harus selalu fresh saat halaman dibuka

Bayangkan kamu ingin membangun fitur pelacak order seperti yang ada di marketplace besar — statusnya bisa berubah kapan saja, jadi SSR adalah pilihan yang tepat.


Memahami Static Site Generation (SSG) di Next.js

SSG berarti HTML di-generate satu kali saat proses npm run build. Hasilnya disimpan sebagai file statis yang bisa langsung disajikan oleh CDN tanpa komputasi tambahan di server.

Di App Router, komponen yang melakukan fetch tanpa cache: 'no-store' secara default akan di-cache (SSG/static):

// app/blog/[slug]/page.tsx
type Params = { slug: string };
type Post = { title: string; content: string; slug: string };

// Tentukan halaman mana saja yang akan di-generate saat build
export async function generateStaticParams(): Promise<Params[]> {
  const res = await fetch('https://api.blog.com/posts');
  const posts: Post[] = await res.json();

  return posts.map((post) => ({
    slug: post.slug,
  }));
}

export default async function BlogPostPage({ params }: { params: Params }) {
  const res = await fetch(`https://api.blog.com/posts/${params.slug}`);
  const post: Post = await res.json();

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

Di Pages Router, SSG menggunakan getStaticProps dan opsional getStaticPaths:

// pages/blog/[slug].tsx
import type { GetStaticProps, GetStaticPaths } from 'next';

type Post = { title: string; content: string; slug: string };

export const getStaticPaths: GetStaticPaths = async () => {
  const res = await fetch('https://api.blog.com/posts');
  const posts: Post[] = await res.json();

  return {
    paths: posts.map((p) => ({ params: { slug: p.slug } })),
    fallback: false, // 404 untuk slug yang tidak ada
  };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const res = await fetch(`https://api.blog.com/posts/${params?.slug}`);
  const post: Post = await res.json();

  return { props: { post } };
};

export default function BlogPost({ post }: { post: Post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

Kapan menggunakan SSG?

  • Konten jarang berubah (dokumentasi, artikel blog, halaman landing)
  • Performa dan kecepatan load adalah prioritas utama
  • Traffic tinggi dan kamu ingin meminimalkan beban server

Jika kamu sedang membangun portal berita teknologi atau dokumentasi produk, SSG + CDN bisa melayani jutaan request tanpa server bekerja keras.


Perbandingan SSR, SSG, dan ISR

Sebelum masuk ke ISR, mari kita lihat perbandingan langsung ketiga strategi ini:

AspekSSRSSGISR
Waktu generate HTMLSetiap requestSaat buildSaat build + background
Kecepatan responsSedangSangat cepatSangat cepat
Kebaruan dataSelalu freshHanya saat buildSesuai interval revalidate
Beban serverTinggiMinimalMinimal
Cocok untukDashboard, real-timeBlog, dokumentasiKatalog, artikel

Jalan Tengah: Incremental Static Regeneration (ISR)

ISR adalah fitur eksklusif Next.js yang menggabungkan keunggulan SSG (kecepatan) dengan fleksibilitas SSR (data bisa diperbarui). Halaman di-generate secara statis, tetapi akan di-regenerate di background setelah interval waktu tertentu.

// App Router — gunakan revalidate
// app/articles/page.tsx
export const revalidate = 60; // regenerate setiap 60 detik

export default async function ArticlesPage() {
  const res = await fetch('https://api.blog.com/articles');
  const articles = await res.json();

  return (
    <ul>
      {articles.map((a: { id: number; title: string }) => (
        <li key={a.id}>{a.title}</li>
      ))}
    </ul>
  );
}
// Pages Router — gunakan revalidate di getStaticProps
import type { GetStaticProps } from 'next';

type Article = { id: number; title: string };

export const getStaticProps: GetStaticProps = async () => {
  const res = await fetch('https://api.blog.com/articles');
  const articles: Article[] = await res.json();

  return {
    props: { articles },
    revalidate: 60, // regenerate setiap 60 detik
  };
};

export default function ArticlesPage({ articles }: { articles: Article[] }) {
  return (
    <ul>
      {articles.map((a) => (
        <li key={a.id}>{a.title}</li>
      ))}
    </ul>
  );
}

ISR sangat ideal untuk halaman yang datanya berubah, tapi tidak perlu selalu real-time — misalnya halaman katalog produk, ranking, atau artikel yang diperbarui beberapa kali sehari.


Contoh Kasus Nyata

HalamanStrategiAlasan
Halaman utama toko onlineSSG + ISRKonten sering sama, performa penting
Halaman detail produkISR (60–300 detik)Stok/harga bisa berubah, tapi bukan real-time
Dashboard penggunaSSRData personal, harus selalu akurat
Artikel blogSSGJarang berubah, performa maksimal
Hasil pencarianSSRQuery unik tiap user
Halaman status orderSSRStatus berubah kapan saja

Jika kamu membangun layanan seperti marketplace skala menengah, kamu bisa menggabungkan ketiganya: halaman listing produk pakai ISR, halaman cart dan checkout pakai SSR, dan halaman statis seperti “Tentang Kami” pakai SSG murni.

Untuk proyek yang lebih kompleks, kamu juga bisa menggabungkan strategi rendering ini dengan teknik optimasi performa seperti yang dibahas di artikel React, karena prinsip-prinsip dasar performa component tetap berlaku di Next.js.

Selain itu, jika kamu berencana men-deploy ke Cloudflare Pages atau menggunakan CDN, penting untuk memahami cara kerja cache agar SSG dan ISR bekerja optimal — lihat panduan optimasi caching di Cloudflare untuk referensi lebih lanjut.


Troubleshooting: Error yang Sering Muncul

Halaman SSG Tidak Update Meskipun Data Sudah Berubah

Penyebab: Halaman menggunakan SSG tanpa ISR, sehingga HTML hanya di-generate saat build. Perubahan data di API tidak otomatis tercermin di halaman yang sudah di-deploy.

Solusi: Tambahkan revalidate untuk mengaktifkan ISR agar halaman diperbarui secara berkala:

// App Router — app/products/page.tsx
export const revalidate = 300; // regenerate setiap 5 menit

export default async function ProductsPage() {
  const res = await fetch('https://api.toko.com/products');
  const products = await res.json();
  // ...
}
// Pages Router — di getStaticProps
export const getStaticProps: GetStaticProps = async () => {
  const res = await fetch('https://api.toko.com/products');
  const products = await res.json();

  return {
    props: { products },
    revalidate: 300, // regenerate setiap 5 menit
  };
};

Error: cookies() / headers() Tidak Bisa Digunakan di Static Page

Penyebab: Kamu mencoba mengakses cookies() atau headers() dari next/headers di dalam halaman atau component yang di-cache sebagai static. Fungsi ini hanya tersedia di dynamic (SSR) context.

Solusi: Tambahkan export const dynamic = 'force-dynamic' untuk memaksa halaman menjadi SSR:

// app/dashboard/page.tsx
import { cookies } from 'next/headers';

// Paksa halaman menjadi dynamic (SSR)
export const dynamic = 'force-dynamic';

export default async function DashboardPage() {
  const cookieStore = cookies();
  const token = cookieStore.get('auth-token');

  if (!token) {
    return <div>Silakan login terlebih dahulu.</div>;
  }

  return <div>Selamat datang di Dashboard!</div>;
}

getStaticPaths Mengembalikan 404 untuk Slug yang Valid

Penyebab: Slug tidak ada di array paths yang dikembalikan getStaticPaths, dan fallback di-set ke false sehingga semua path di luar daftar langsung di-404.

Solusi: Ubah fallback ke 'blocking' agar slug baru di-generate secara on-demand:

// pages/blog/[slug].tsx
export const getStaticPaths: GetStaticPaths = async () => {
  const res = await fetch('https://api.blog.com/posts');
  const posts = await res.json();

  return {
    paths: posts.map((p: { slug: string }) => ({
      params: { slug: p.slug },
    })),
    // 'blocking' = slug baru di-generate on-demand, bukan langsung 404
    fallback: 'blocking',
  };
};

Pertanyaan yang Sering Diajukan

Apa perbedaan utama antara SSR dan SSG di Next.js?

SSR men-generate HTML di server setiap kali ada request, sehingga data selalu fresh tapi ada overhead komputasi per request. SSG men-generate HTML satu kali saat build dan hasilnya disajikan langsung dari CDN — sangat cepat, tapi data hanya seakurat saat terakhir build. Keduanya menghasilkan HTML yang SEO-friendly, perbedaannya ada pada kapan HTML itu dibuat.

Bagaimana cara memilih antara SSR, SSG, dan ISR?

Gunakan SSG jika kontenmu jarang berubah dan tidak bergantung pada data pengguna. Gunakan ISR jika konten berubah tapi tidak harus real-time — misalnya katalog produk atau artikel. Gunakan SSR jika data sangat dinamis atau bersifat personal, seperti dashboard atau halaman yang bergantung pada siapa yang sedang login.

Apakah bisa menggunakan SSR dan SSG di halaman berbeda dalam satu project Next.js?

Ya, Next.js memang dirancang untuk ini. Setiap halaman (page.tsx di App Router, atau file di pages/ di Pages Router) bisa memiliki strategi rendering yang berbeda secara independen. Kamu bisa punya halaman blog yang SSG, dashboard yang SSR, dan halaman produk yang ISR — semua dalam satu project yang sama.

Mengapa halaman SSG lebih cepat dari SSR?

Karena halaman SSG sudah berupa file HTML statis yang disimpan di CDN. Saat pengguna membuka halaman, CDN langsung mengirim file tersebut tanpa perlu menunggu server menjalankan kode atau mengambil data dari database. Latensi jauh lebih rendah karena tidak ada round-trip ke server aplikasi.

Bagaimana ISR bekerja saat periode revalidate belum habis?

Selama periode revalidate belum habis, Next.js tetap menyajikan versi HTML yang di-cache tanpa memicu regenerasi. Setelah waktu revalidate berlalu, request pertama yang masuk akan memicu proses regenerasi di background — pengguna itu masih mendapat versi lama, tetapi request berikutnya sudah mendapat versi HTML yang baru.


Kesimpulan

SSR, SSG, dan ISR bukan pilihan yang bersaing — melainkan alat yang saling melengkapi. Next.js memberimu kebebasan untuk menggunakan strategi yang paling tepat di setiap halaman dalam satu project yang sama.

Ringkasnya:

  • SSG → konten statis, performa maksimal, ideal untuk blog dan dokumentasi
  • ISR → konten semi-dinamis, keseimbangan sempurna antara kecepatan dan kebaruan data
  • SSR → konten real-time atau personal, cocok untuk dashboard dan fitur yang bergantung pada login

Semakin kamu memahami kebutuhan data di setiap halaman, semakin tepat pilihanmu — dan itu langsung berdampak pada performa, biaya server, dan pengalaman pengguna. Selamat bereksperimen, dan jangan ragu untuk eksplorasi artikel lainnya di KamusNgoding untuk terus mengasah kemampuanmu membangun aplikasi Next.js yang optimal!

Artikel Terkait