Sekarang kita ngobrol santai yuk soal Next.js 15 dan fitur-fitur serunya di balik layar. Bayangin dunia web itu kayak kereta cepat yang terus melaju โ setiap versi baru, ibaratnya kayak stasiun baru yang penuh kejutan dan teknologi canggih. Nah, Next.js 15 ini salah satu stasiun terbarunya yang bawa banyak peningkatan seru, terutama soal gimana cara kita ngatur dan "ngobrol" sama URL.
Pernah nggak kamu buka sebuah website, terus lihat alamatnya ada tanda tanya (?) diikuti tulisan panjang kayak tokoonline.com/produk?kategori=elektronik&warna=merah&page=2? Nah, itu namanya query parameters. Gampangnya, itu kayak catatan kecil yang kamu tempel di paket pengiriman. Catatan itu bilang, "Saya mau produk elektronik, warnanya merah, halaman 2 ya."
Dalam aplikasi web zaman sekarang, query parameter ini penting banget. Fungsinya macam-macam: nyari data, filter produk, pagination, bahkan tracking iklan. Dan yang paling keren โ kita bisa lakuin itu semua tanpa bikin halaman baru!
Nah, di Next.js 15, ada dua "alat bantu" yang bisa bantu kamu ngatur urusan URL ini dengan gampang dan efisien, yaitu searchParams dan useSearchParams.
searchParamsdipakai di server pas halaman pertama kali dimuat.useSearchParamsdipakai di client buat interaksi dinamis tanpa reload.
Kalau kamu udah paham cara kerja dua alat ini, bikin aplikasi yang cepat, interaktif, dan SEO-friendly jadi jauh lebih gampang. Jadi, kalau kamu pengen naik level sebagai developer Next.js, ngerti duo ini tuh bukan lagi bonus โ tapi kebutuhan dasar. ๐ช
๐งญ Penjelasan searchParams dan useSearchParams

Kamu pasti pernah ngalamin ini: buka sebuah website, terus tiba-tiba URL-nya jadi panjang dan ada tanda tanya di dalamnya. Misalnya:
<https://tokoonline.com/produk?q=nextjs&kategori=buku&page=2>
Nah, bagian setelah tanda ? itu disebut query parameters. Mereka kayak catatan kecil yang dikirim bareng alamat website, buat ngasih tahu server: โEh, gue mau nyari produk dengan kata kunci โnextjsโ, kategorinya buku, dan tolong tampilkan halaman kedua ya!โ
๐ Analogi: Lagi Cari Buku di Perpustakaan Online
Bayangin kamu lagi main ke perpustakaan digital raksasa. Kamu pengen cari buku tentang โNext.jsโ. Kamu ketik di kolom pencarian, klik enter, dan boom โ muncul hasil pencarian yang sesuai.
Tapi bukan cuma halaman yang berubah, URL-nya juga ikut berubah jadi kayak:
<https://perpusdigital.com/search?q=nextjs>
Nah, di balik layar, browser ngasih tahu server, โTolong carikan buku yang cocok dengan kata kunci nextjs ya.โ Dan semua itu dikomunikasikan lewat yang namanya query params.
๐ฏ Nah, di sinilah searchParams dan useSearchParams Masuk
Di Next.js 15, kamu bisa ngambil isi query params itu dengan dua cara:
searchParams: cocok buat komponen server. Jadi sebelum halaman dikirim ke browser, datanya udah siap duluan.useSearchParams: cocok buat komponen client. Jadi kamu bisa baca dan ubah isi URL secara dinamis, misalnya buat fitur filter tanpa reload.
Keduanya bisa bantu kamu bikin aplikasi yang lebih responsif, cepat, dan terasa pintar.
๐ณ Si Koki dan Asisten: Mengenal searchParams & useSearchParams
Di dunia Next.js 15, ada dua โpemain andalanโ yang sering banget dipakai buat ngurusin yang namanya query parameters: searchParams dan useSearchParams. Mereka punya peran masing-masing โ satu kerja di dapur (server), satu lagi bantu-bantu di ruang makan (client). Kerjanya beda, tapi saling ngisi.
๐จโ๐ณ searchParams: Si Koki Handal di Dapur Server
Coba bayangin kamu lagi jadi koki di restoran. Sebelum mulai masak, tentu kamu butuh tahu pesanan pelanggan dulu, kan? Nah, pesanan itu datang dalam bentuk catatan kecil โ semacam daftar belanja.
Di Next.js 15, searchParams itu kayak daftar pesanan tadi, tapi khusus buat komponen server. Misalnya nih, kamu buka halaman https://tokoonline.com/produk?kategori=baju&warna=biru, si server langsung baca parameter kategori=baju dan warna=biru dari URL.
Dengan informasi itu, server bisa langsung nyiapin โhidanganโ yang pas: produk baju warna biru langsung ditampilkan dari awal. Jadi, pas sampai ke browser, semuanya udah siap โ gak perlu masak-masak lagi di tempat. Ini bikin halaman tampil lebih cepat, dan Google juga seneng karena datanya udah lengkap pas di-load (alias SEO-friendly).
Contoh kode sederhana di Server Component:
// app/produk/page.tsx
interface ProdukPageProps {
searchParams?: Promise<{
kategori?: string;
warna?: string;
}>;
}
export default async function ProdukPage({ searchParams }: ProdukPageProps) {
const params = await searchParams;
const kategoriDipilih = params?.kategori ?? "Semua";
const warnaDipilih = params?.warna ?? "Tidak Ada";
const res = await fetch(
`https://api.contoh.com/produk?kategori=${encodeURIComponent(
kategoriDipilih
)}&warna=${encodeURIComponent(warnaDipilih)}`
);
if (!res.ok) {
throw new Error("Gagal mengambil data produk");
}
const produk: {
id: string;
nama: string;
harga: string;
}[] = await res.json();
return (
<div>
<h1>
Daftar Produk ({kategoriDipilih} - {warnaDipilih})
</h1>
<ul>
{produk.map((item) => (
<li key={item.id}>
{item.nama} - {item.harga}
</li>
))}
</ul>
</div>
);
}
Di sini, searchParams langsung kita akses di fungsi ProdukPage yang merupakan Server Component. Kita bisa langsung tahu query kategori dan warna yang diminta pengguna untuk mengambil data yang relevan. Keren, kan?
๐งโ๐ผ useSearchParams: Asisten Pribadi yang Selalu Siaga (Client Components)
Sekarang, gimana kalau kamu mau ubah filter kategori setelah halaman tampil โ misalnya ganti warna atau urutan produk โ tapi nggak pengen halaman nge-reload ulang dari awal?
Nah, di sinilah useSearchParams jadi penyelamat. Bayangin dia kayak asisten pribadi yang selalu nongkrong di samping kamu, langsung nyatet setiap perubahan kecil di URL โ tanpa harus repot-repot minta bantuan dapur (alias server).
Karena dia hidup di Client Components, useSearchParams cocok banget buat fitur-fitur yang butuh interaksi cepat dan mulus: filter produk, tab dinamis, pencarian langsung, dan semacamnya. Misalnya kamu klik tombol โWarna: Biruโ, si asisten langsung nyimpen perubahan itu di URL dan ngasih tahu komponen lain buat update tampilannya.
Hasilnya? Halaman terasa ringan, responsif, dan nggak bikin pengguna nunggu lama. Cocok banget buat user experience yang modern dan kece! โก๏ธ
Contoh kode sederhana di Client Component:
// components/FilterProduk.tsx
'use client'; // Penting: menandakan ini adalah Client Component
import { useRouter, useSearchParams } from 'next/navigation';
import React, { useState } from 'react';
export default function FilterProduk() {
const router = useRouter();
const searchParams = useSearchParams();
const [kategoriTerpilih, setKategoriTerpilih] = useState(searchParams.get('kategori') || '');
const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const newKategori = e.target.value;
setKategoriTerpilih(newKategori);
// Buat URLSearchParams baru untuk memodifikasi query params
const params = new URLSearchParams(searchParams.toString());
if (newKategori) {
params.set('kategori', newKategori);
} else {
params.delete('kategori');
}
// Perbarui URL tanpa reload halaman
router.push(`?${params.toString()}`);
};
return (
<div>
<label htmlFor="kategori">Filter Kategori:</label>
<select id="kategori" value={kategoriTerpilih} onChange={handleFilterChange}>
<option value="">Semua Kategori</option>
<option value="elektronik">Elektronik</option>
<option value="pakaian">Pakaian</option>
<option value="makanan">Makanan</option>
</select>
</div>
);
}
Di sini, kita pakai useSearchParams untuk membaca query parameter kategori saat komponen dimuat. Lalu, saat pengguna mengubah filter, kita pakai router.push (dengan bantuan URLSearchParams untuk memodifikasi query) untuk memperbarui URL di browser tanpa harus reload halaman. Asisten pribadi kita ini cekatan banget, kan?
Jadi, intinya: searchParams itu untuk "daftar belanja" awal yang dibaca server, sementara useSearchParams itu asisten pribadi di browser yang siap sedia kalau kamu mau memodifikasi "catatan tempel" di URL secara dinamis.
๐ Kenapa Harus Peduli Sama searchParams dan useSearchParams?
Oke, tadi kita udah kenalan sama dua tokoh penting: si koki server (searchParams) dan asisten pribadi client (useSearchParams). Tapi pertanyaannya sekarang: โKenapa sih harus repot-repot pakai mereka?โ
Jawabannya simpel: karena mereka berdua ini kayak duo superhero yang bikin aplikasi Next.js kamu jadi:
- Lebih cepat,
- Lebih cerdas,
- Lebih nyaman buat user,
- Dan tentunya... lebih disayang Google.
Yuk kita bedah satu-satu kekuatannya! ๐ช
๐ 1. Performa Ngebut & Disayang Google (SEO Friendly)
Bayangin kamu mau nonton video di YouTube. Begitu klik link, jreng! videonya langsung muncul. Gak ada drama loading muter-muter dulu.
Nah, efek โcepat tampilโ itu juga bisa kamu dapat di aplikasi kamu, berkat searchParams yang kerja di Server Components.
Contohnya:
<https://toko.com/produk?kategori=elektronik>
Pas user buka URL itu, searchParams langsung kasih tahu server, โEh, tampilkan produk elektronik ya!โ Jadi Next.js bisa render halaman lengkap dari sisi server sebelum dikirim ke browser.
Hasilnya?
- Halaman muncul lebih cepat
- Gak perlu nunggu fetch data di client
- SEO jadi lebih optimal karena Google bisa langsung baca konten lengkap dari awal
๐ฏ 2. Pengalaman Pengguna yang Lebih Mulus (Responsif Tanpa Reload)
Pernah nggak sih kamu pakai aplikasi yang tiap kali klik tombol filter, halaman langsung blink โ nge-reload, terus balik lagi dari awal? Rasanya kayak lagi asyik baca, eh... bukunya direbut orang. Nyebelin.
Nah, inilah kenapa useSearchParams jadi penyelamat. Karena dia bekerja di Client Components, kamu bisa ubah query parameters langsung dari browser tanpa harus reload seluruh halaman.
Bayangin kamu lagi buka aplikasi edit foto. Kamu klik filter โVintageโ โ fotonya berubah. Terus coba โBlack & Whiteโ โ berubah lagi. Gak ada tuh halaman nge-flash atau loading muter-muter. Semuanya terasa instan dan mulus.
Ini semua karena useSearchParams bisa:
- Baca isi query langsung dari URL
- Dengar perubahan yang terjadi
- Update tampilan UI tanpa perlu minta bantuan server
Hasilnya? UX jadi lebih cepat, ringan, dan bikin betah.
๐งฉ 3. Fleksibel Banget! Semua Diatur dari URL
Bayangin kamu punya toko online. Pelanggan bisa cari produk, filter berdasarkan harga, kategori, merek, ukuran, bahkan atur berapa banyak produk yang mau ditampilkan per halaman.
Sekarang bayangin lagi kalau tiap kombinasi itu harus punya halaman sendiri... Waduh, bisa pusing tujuh keliling!
Untungnya, degan searchParams dan useSearchParams, kamu cuma butuh satu halaman produk, dan semuanya dikendalikan lewat query parameters. Gak perlu bikin halaman baru untuk setiap pilihan โ cukup baca isi URL, dan biarkan aplikasi kamu menyesuaikan tampilan.
Misalnya:
- Filter produk:
?kategori=baju&ukuran=M - Pencarian kata kunci:
?q=sepatu+olahraga - Pagination:
?page=3&limit=10 - Sortir produk:
?sortir_by=harga&order=asc
Keren, kan?
Semua informasi itu bisa langsung dibaca dari URL, dan aplikasi kamu bisa:
- Menampilkan data yang relevan
- Tetap responsif
- Dan bonusnya... URL-nya bisa di-bookmark atau dibagikan ke orang lain!
Ini yang bikin aplikasi kamu terasa cerdas dan fleksibel โ cukup satu halaman, tapi bisa menangani banyak skenario.
๐ 4. URL yang Konsisten = Bisa Di-Bookmark & Dibagikan
Nah, yang satu ini penting banget tapi sering diremehkan. Ketika URL aplikasi kamu selalu mencerminkan kondisi halaman saat ini โ entah itu hasil pencarian, filter, atau urutan produk โ maka kamu udah punya URL yang bisa di-bookmark.
Artinya, user bisa simpan halaman dengan setting favorit mereka. Misalnya, โaku suka produk elektronik warna hitam dengan harga termurahโ โ tinggal simpan aja URL-nya. Nanti kalau mau balik, gak perlu atur ulang dari nol. Praktis banget, kan?
Lebih serunya lagi, URL yang rapi dan konsisten itu juga mudah dibagikan. Temanmu nemu produk kece setelah filter sana-sini? Tinggal copy-paste link-nya dan kirim ke kamu. Begitu kamu buka, voilร โ tampilannya persis sama kayak yang dia lihat.
Itu yang bikin pengalaman pengguna jadi seamless dan profesional banget.
๐๏ธ Membangun Proyek Toko Online: Studi Kasus Penerapan
Setelah kenalan dengan searchParams dan useSearchParams, sekarang saatnya kita masuk ke arena praktik. Anggap aja kamu lagi mau bikin toko online keren dan responsif โ dan kita bakal fokus ke halaman produk, tempat user bisa dengan mudah nyaring barang berdasarkan kategori.
Tujuannya: bikin halaman produk yang bisa menampilkan hasil berbeda tergantung query parameters di URL, seperti ?kategori=baju atau ?warna=hitam.
๐ง 1. Buat Proyek Next.js Baru
Langkah pertama tentu aja bikin proyek Next.js. Buka terminal dan ketik:
npx create-next-app@latest bwa-dev
### atau
bunx create-next-app@latest bwa-dev
Akan muncul beberapa pertanyaan, silahkan pilih seperti berikut ini:
What is your project named? my-next-app (Anda bisa ganti sesuai nama proyek Anda)
Would you like to use TypeScript? Yes (PENTING! Pilih 'Yes' untuk menggunakan TypeScript)
Would you like to use ESLint? Yes (Ini bagus untuk menjaga kualitas kode)
Would you like to use Tailwind CSS? Yes (Sangat direkomendasikan untuk styling cepat dan modern!)
Would you like to use `src/` directory? No (Pilih 'Yes' jika Anda suka struktur 'src' terpisah)
Would you like to use App Router? (recommended) Yes (INI PENTING! Pastikan pilih 'Yes' untuk Next.js 15)
Would you like to use Turbopack for `next dev`? Yes
Would you like to customize the default import alias (@/*)? No
Tunggu hingga proses selesai, kemudian masuk ke direktori proyek:
cd bwa-dev
Jalan kan server:
bun dev
### atau
npm run dev
๐งฉ 2. Setup Shadcn UI
Supaya tampilan toko online kita makin kece dan gak perlu repot bikin komponen dari nol, kita akan pakai Shadcn UI โ komponen UI modern berbasis Tailwind CSS yang gampang banget dipakai.
Pertama, inisialisasi Shadcn UI di proyek kamu dengan salah satu perintah berikut (pilih sesuai package manager yang kamu pakai):
bunx shadcn@latest init
# atau
npx shadcn@latest init
Ikuti petunjuk yang muncul di terminal untuk setup awalnya (biasanya kamu diminta pilih components folder dan preset Tailwind).
Setelah setup selesai, yuk coba tambahkan satu komponen dulu, misalnya button:
bunx shadcn@latest add button
# atau
npx shadcn@latest add button
Kalau berhasil, kamu akan lihat file button muncul di folder components/ui/button.tsx.
Shadcn siap digunakan! ๐ Sekarang komponen UI kamu bakal terlihat rapi, konsisten, dan enak dilihat โ cocok buat proyek e-commerce.
๐ 3. Setup Endpoint API Dummy JSON
Supaya toko online kita bisa tampilkan produk yang โbeneranโ (meskipun dummy), kita akan ambil data dari dummyjson.com โ layanan API publik yang cocok buat latihan seperti ini.
Pertama, buat dulu sebuah file di:
src/lib/endpoint.ts
Lalu isi dengan kode berikut:
export const urlAllProduct = "<https://dummyjson.com/products>";
export const urlAllCategories = `${urlAllProduct}/categories`;
export const urlProductByCategory = (category: string) =>
`${urlAllProduct}/category/${category}`;
Apa sih fungsinya?
urlAllProduct: untuk ambil semua produk.urlAllCategories: untuk ambil daftar kategori yang tersedia.urlProductByCategory: untuk ambil produk berdasarkan kategori tertentu, misalnyahttps://dummyjson.com/products/category/smartphones.
Dengan struktur seperti ini, kita bisa dengan mudah ambil data sesuai kebutuhan, dan semuanya tetap rapi dan reusable. ๐
๐ 4. Setup Server Actions
Sekarang kita masuk ke bagian penting: mengambil data dari API di sisi server. Ini artinya, data kita udah siap sebelum halaman dikirim ke browser โ lebih cepat, lebih SEO-friendly.
Kita akan pakai fitur Server Actions bawaan Next.js ("use server"), yang bikin proses fetch data di server jadi lebih clean dan modern.
๐ฆ Ambil Semua Produk
Pertama, kita ambil daftar semua produk. Buat file:
src/actions/product/getAll.ts
Lalu isi dengan kode berikut:
"use server";
import { ProductList } from "@/types/product";
import { urlAllProduct } from "@/lib/endpoint";
export async function getAllProducts(): Promise<ProductList> {
const data = await fetch(urlAllProduct);
return await data.json();
}
Fungsi getAllProducts() ini akan fetch semua produk dari API langsung di sisi server, jadi nggak perlu nungguin browser ambil data sendiri.
๐ Ambil Produk Berdasarkan Kategori
Sekarang, kita tambahkan fungsi untuk ambil produk per kategori. Buat file:
src/actions/product/getByCategory.ts
Lalu masukkan kode ini:
"use server";
import { ProductList } from "@/types/product";
import { urlProductByCategory } from "@/lib/endpoint";
export async function getProductByCategory(
category: string
): Promise<ProductList> {
const data = await fetch(urlProductByCategory(category));
return await data.json();
}
Jadi nanti kalau URL-nya ada ?kategori=smartphones, kita bisa panggil fungsi ini buat ambil data yang sesuai. Simpel dan terstruktur, kan?
๐๏ธ Ambil Daftar Semua Kategori
Terakhir, kita butuh daftar semua kategori produk biar user bisa pilih filter. Buat file:
src/actions/category/getAll.ts
Isi dengan kode ini:
"use server";
import { Category } from "@/types/category";
import { urlAllCategories } from "@/lib/endpoint";
export async function getAllCategories(): Promise<Category[]> {
const data = await fetch(urlAllCategories);
return await data.json();
}
Fungsi ini juga jalan di sisi server, jadi pas halaman dimuat, daftar kategorinya udah langsung siap buat ditampilkan ke user.
๐งฑ 5. Buat Komponen
Sekarang saatnya kita masuk ke bagian visual alias tampilan. Kita akan bikin beberapa komponen yang akan jadi tulang punggung halaman produk: kategori, daftar produk, dan kartu produk.
๐ท๏ธ Komponen CategoryList
Pertama, buat file:
src/components/category-list.tsx
Lalu salin kode berikut:
import { getAllCategories } from "@/actions/category/getAll";
import CategoryCard from "./category";
export async function CategoryList() {
const data = await getAllCategories();
return (
<div className="flex flex-wrap gap-2 m-5">
<CategoryCard categories={data} />
</div>
);
}
Apa yang dilakukan komponen ini?
- Mengambil data kategori dari server (pakai Server Component)
- Meneruskannya ke
CategoryCard - Menampilkan tombol-tombol kategori dalam tampilan fleksibel
๐๏ธ Komponen CategoryCard
Sekarang buat file:
src/components/category.tsx
Salin kode ini:
"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { Category } from "@/types/category";
import { Button } from "./ui/button";
interface CategoryCardProps {
categories: Category[];
}
export default function CategoryCard({ categories }: CategoryCardProps) {
const router = useRouter();
const searchParams = useSearchParams();
const selected = searchParams.get("kategori");
const updateCategory = (slug?: string) => {
const params = new URLSearchParams(searchParams.toString());
if (!slug || slug === selected) {
params.delete("kategori");
} else {
params.set("kategori", slug);
}
router.push(`?${params.toString()}`);
};
return (
<div className="flex flex-wrap gap-2">
<ButtononClick={() => updateCategory()}
variant={selected ? "outline" : "default"}
>
All
</Button>
{categories.map((category) => {
const isActive = category.slug === selected;
return (
<Buttonkey={category.slug}
onClick={() => updateCategory(category.slug)}
variant={isActive ? "default" : "outline"}
>
{category.name}
</Button>
);
})}
</div>
);
}
Fungsinya apa?
- Menampilkan daftar kategori dalam bentuk tombol
- Mengubah URL saat tombol diklik (pakai
useSearchParams) - Secara otomatis menyaring produk berdasarkan kategori
๐ Komponen ProductList
Lanjut, buat file:
src/components/product-list.tsx
Isi dengan:
import { getProductByCategory } from "@/actions/product/getByCategory";
import { ProductCard } from "./product-card";
import { getAllProducts } from "@/actions/product/getAll";
interface ProductListProps {
kategori?: string;
}
export async function ProductList({ kategori }: ProductListProps) {
const allProducts = kategori
? await getProductByCategory(kategori)
: await getAllProducts();
if (allProducts.products.length === 0) {
return (
<div className="text-center text-muted-foreground py-12">
Tidak ada produk ditemukan.
</div>
);
}
return (
<div className="mx-5 my-8 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-6">
{allProducts.products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
Fungsi utamanya:
- Ambil semua produk atau produk per kategori
- Tampilkan daftar produk dalam bentuk grid
- Munculkan fallback teks kalau produk kosong
๐งพ Komponen ProductCard
Terakhir, buat file:
src/components/product-card.tsx
Lalu salin:
import { Product } from "@/types/product";
import Image from "next/image";
interface ProductCardProps {
product: Product;
}
export function ProductCard({ product }: ProductCardProps) {
const {
title,
description,
price,
discountPercentage,
rating,
stock,
brand,
thumbnail,
tags,
} = product;
const discountedPrice = (price * (1 - discountPercentage / 100)).toFixed(2);
return (
<div className="rounded-2xl border p-4 shadow-sm hover:shadow-md transition">
<div className="relative w-full h-48 rounded-xl overflow-hidden">
<Image src={thumbnail} alt={title} fill className="object-cover" />
</div>
<div className="mt-4">
<h2 className="text-lg font-semibold line-clamp-1">{title}</h2>
<p className="text-sm text-muted-foreground line-clamp-2">
{description}
</p>
<p className="text-sm mt-1 text-gray-500">Brand: {brand}</p>
<div className="mt-2 flex items-center gap-2">
<span className="text-primary font-semibold text-lg">
${discountedPrice}
</span>
{discountPercentage > 0 && (
<span className="line-through text-sm text-muted-foreground">
${price}
</span>
)}
</div>
<div className="mt-2 flex items-center justify-between text-sm text-gray-500">
<span>โญ {rating}</span>
<span>{stock} stok</span>
</div>
{tags.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1">
{tags.slice(0, 3).map((tag) => (
<spankey={tag}
className="bg-gray-100 dark:bg-gray-800 text-xs px-2 py-0.5 rounded-full"
>
#{tag}
</span>
))}
</div>
)}
</div>
</div>
);
}
Tugas ProductCard:
- Menampilkan info produk dalam satu kartu: gambar, nama, deskripsi, harga, rating, stok, dan tag
- Komponen visual utama untuk katalog produk kamu
Semua komponen sudah siap! ๐
๐๏ธ 6. Buat Halaman Produk
Sekarang saatnya kita satukan semuanya ke dalam halaman produk. Halaman ini bakal jadi pusatnya โ menampilkan filter kategori di atas, dan daftar produk di bawahnya sesuai pilihan user.
Langkah pertama, buat file:
src/app/product/page.tsx
Lalu isi dengan kode berikut:
import { CategoryList } from "@/components/category-list";
import { ProductList } from "@/components/product-list";
import { Suspense } from "react";
interface ProductPageProps {
searchParams: Promise<{
kategori?: string;
}>;
}
export default async function ProductPage({ searchParams }: ProductPageProps) {
const { kategori } = await searchParams;
return (
<div>
<Suspense fallback={<p>Loading kategori...</p>}>
<CategoryList />
</Suspense>
<Suspense fallback={<p>Loading produk...</p>}>
<ProductList kategori={kategori} />
</Suspense>
</div>
);
}
๐ง Penjelasan Singkat:
searchParams: digunakan untuk mengambil query dari URL, dalam hal ini?kategori=....CategoryList: menampilkan daftar tombol filter kategori (komponen server-side).ProductList: menampilkan daftar produk sesuai kategori yang dipilih.Suspense: bikin setiap bagian punya loading state sendiri, jadi UX-nya lebih smooth โ kategori bisa muncul duluan tanpa nunggu produk selesai di-fetch.
Dengan cara ini, halaman produk kamu:
- Lebih interaktif
- Tetap SEO-friendly (karena banyak di-render di server)
- Dan punya UX yang mulus berkat
Suspense
๐ Jalankan dan Lihat Hasilnya
Sekarang saatnya lihat hasil kerja keras kita!
Jalankan server Next.js kamu (pakai bun dev atau npm run dev), lalu buka halaman:
๐ http://localhost:3000/product
Tampilan awalnya bakal kelihatan seperti ini:

๐งช Coba Klik Salah Satu Kategori
Sekarang coba klik salah satu tombol kategori โ misalnya Laptops. Maka:
- Produk yang tampil akan langsung difilter sesuai kategori
- URL akan otomatis berubah jadi:
<http://localhost:3000/product?kategori=laptops>
Dan hasilnya kira-kira seperti ini:

Dengan fitur ini:
- User bisa menyaring produk secara instan tanpa reload halaman
- URL bisa di-bookmark dan dibagikan ke orang lain
- Aplikasi terasa cepat dan interaktif
๐ Kesalahan Umum yang Sering Terjadi (dan Gimana Cara Benerinnya)
โNamanya juga belajar, salah itu wajar. Tapi yang penting... kita tahu di mana salahnya, dan bisa benerin tanpa panik.โ
Kalau kamu lagi ngulik searchParams atau useSearchParams di Next.js, besar kemungkinan kamu bakal ngalamin hal-hal yang bikin garuk-garuk kepala. Tapi tenang, kamu nggak sendirian! Di bagian ini, kita bahas jebakan-jebakan yang sering banget dialami, plus solusi dan contoh kodenya. Biar kamu bisa senyum-senyum aja pas nemu error nanti. ๐
โ 1. Pakai useSearchParams di Server Component
Masalah:
โKok error ya pas aku pakai useSearchParams() di komponenku?โ
Ini klasik banget. Soalnya useSearchParams() adalah React Hook yang cuma boleh dipakai di Client Component. Kalau kamu pakai di Server Component tanpa deklarasi use client, hasilnya? Boom! Error merah nyala di layar.
Solusi:
- Tambahin
'use client'di bagian paling atas file - Atau, kalau kamu di Server Component, cukup pakai
searchParamsdari props aja
Contoh Kesalahan:
// โ Ini akan error karena bukan client component
import { useSearchParams } from 'next/navigation'
export default function Page() {
const params = useSearchParams() // ๐ฅ Error!
return <div>{params.get('q')}</div>
}
๐งน 2. Nggak Validasi searchParams
Masalah:
โKok tiba-tiba error pas buka URL yang dikirim orang?โ
Karena... semua orang bisa edit URL seenaknya. Kamu harus anggap query string sebagai input dari user, dan semua input user wajib divalidasi. Jangan anggap remeh ya, ini soal keamanan juga!
Solusi:
- Pakai pengecekan manual, atau schema validation kayak Zod atau Yup
- Jangan langsung pakai nilai dari URL buat nge-fetch data tanpa dicek dulu
Contoh Validasi Manual:
const search = searchParams.q?.toLowerCase() || ''
if (search.length > 100) {
throw new Error('Pencarian terlalu panjang')
}
Contoh Validasi Pakai Zod:
import { z } from 'zod'
const querySchema = z.object({
q: z.string().max(100).optional(),
})
const safeQuery = querySchema.safeParse(searchParams)
if (!safeQuery.success) {
// Bisa kasih fallback, atau error message
}
๐ 3. URL Jadi Aneh Setelah Diubah
Masalah:
โKenapa setelah klik filter, URL-nya jadi berantakan kayak ?warna=hitam=&=&sort=asc?โ
Biasanya karena kita terlalu semangat ngedit URL pakai string concat (+ '&key=value') tanpa bantuan yang bener.
Solusi:
- Gunakan
URLSearchParamsuntuk nambah, update, atau hapus query dengan rapi - Hindari manipulasi string URL secara manual
Contoh Rapi Pakai URLSearchParams:
const params = new URLSearchParams(window.location.search)
params.set('warna', 'hitam')
params.set('ukuran', 'L')
router.push(`?${params.toString()}`) // โจ Bersih dan aman
๐ฆ 4. Over-fetching atau Under-fetching Data
Masalah:
โKok data yang muncul kebanyakan... atau malah nggak ada?โ
Biasanya ini karena kamu nggak nyambungin logika fetch data dengan benar ke searchParams yang masuk.
Solusi:
- Pastikan ambil parameter dari URL dengan benar
- Gunakan fallback default biar aplikasi tetap jalan walau query-nya kosong
Contoh Perbaikan:
export default async function Page({ searchParams }) {
const kategori = searchParams.kategori || 'semua'
const data = await getProduk({ kategori })
return <ListProduk items={data} />
}
๐ฏ Intinya...
Jangan buru-buru nyalahin Next.js. Kadang bukan karena tools-nya yang error, tapi kitanya yang salah "naro alat" โ harusnya di client, malah di server. Atau sebaliknya. ๐
Kalau kamu udah tahu jebakan-jebakan ini, kamu bisa lebih percaya diri pakai searchParams dan useSearchParams. Anggap aja mereka seperti pedang โ kalau tahu cara pakainya, bisa jadi senjata andalan untuk bikin web yang cepat, fleksibel, dan enak dipakai. ๐ช
๐ Penutup: Yuk, Ubah URL Jadi Lebih Bermakna
Setelah kita jalan-jalan bareng bahas searchParams dan useSearchParams, semoga kamu makin sadar bahwa ini bukan cuma fitur kecil di Next.js. Justru dua hal ini jadi kunci penting buat bikin aplikasi yang terasa hidup, fleksibel, dan ramah pengguna.
๐ Rangkuman Kilat
searchParamscocok dipakai di Server Components โ pas buat SEO, pre-render, dan fetching data di awal halaman.useSearchParamscocok di Client Components โ mantap buat filter real-time, interaksi dinamis, dan perubahan URL tanpa reload.- Selalu validasi isi query dari URL biar aman dan rapi.
- Gunakan
URLSearchParamsuntuk manipulasi query string tanpa pusing-pusing.
Intinya, di Next.js 15, URL itu bukan cuma alamat. Dia bisa jadi tempat nyimpen "state" aplikasi kamu. Mulai dari pencarian, filter, halaman, hingga sorting โ semua bisa diatur lewat query params. Dan dengan alat yang tepat, semuanya jadi mulus.
๐ Belajar Langkah Demi Langkah di BuildWithAngga
Kalau kamu ngerasa artikel ini seru, tapi pengen belajar lebih dalam dan sambil praktek langsung, yuk gabung di kelas **Next.js bareng BuildWithAngga**!
Di kelas itu kamu bisa:
โ Bangun proyek nyata bareng mentor
โ Belajar dari dasar sampai mahir
โ
Ngulik fitur-fitur modern kayak searchParams, Server Actions, hingga Partial Prerendering
โ Gabung ke komunitas yang suportif dan aktif
โKarena belajar coding itu bukan soal seberapa banyak yang kamu hafal, tapi seberapa sering kamu praktik.โ