Cara membuat bracket turnamen bulu tangkis interaktif dengan Next.js

Table of Contents
BWF World Junior Championships 2026 berlangsung di Kairo pada 12 sampai 17 Oktober, sesuai halaman turnamen di situs BWF. Kalau klub kamu menggelar turnamen internal dengan bagan yang digambar di kertas atau di spreadsheet, bagan itu harus difoto ulang dan dikirim ke grup setiap kali ada pemenang baru.
Jawaban singkat
Untuk turnamen sekali jalan, generator bracket gratis sudah cukup. Bracket buatan sendiri dengan Next.js masuk akal kalau klub ingin bagan itu ada di website-nya sendiri, dengan URL, data, dan tampilan milik klub, serta pemenang yang naik ke babak berikutnya secara otomatis. Project di tutorial ini terdiri dari empat bagian, yaitu pasangan perempat final, pasangan babak berikutnya yang diambil dari pemenang, tampilan tiga babak, dan logika memilih pemenang.

Kapan cukup pakai generator gratis, kapan perlu bracket sendiri?
Sebelum memilih, jawab tiga pertanyaan berikut. Apakah hasil turnamen perlu tetap bisa dibuka di website klub tahun depan? Apakah bracket perlu tampil dengan nama, warna, dan alamat website klub sendiri? Apakah ada orang yang bisa merawat kodenya setelah turnamen selesai? Kalau ketiga jawabannya tidak, generator gratis lebih hemat waktu.

Hasil akhir: bracket yang terisi otomatis
Hasilnya berupa bracket sistem gugur untuk 8 pemain dengan tiga babak, yaitu perempat final, semifinal, dan final. Pemenang yang kamu pilih otomatis muncul di babak berikutnya, dan nama pemain di demo adalah nama contoh (fiktif). Saya membangun demo ini dengan bantuan AI coding agent, lalu hasilnya saya cek sendiri.
Yang kamu perlukan
Versi yang dipakai adalah Next.js 16.4.2 (App Router), React 19.3.0, dan Tailwind CSS v4, jadi kamu perlu Node.js yang bisa menjalankan versi Next.js itu dan pemahaman dasar tentang komponen React dan hook seperti useState. Kalau App Router masih baru buat kamu, baca dulu App Router dalam praktik.
Langkah 1: tulis pasangan perempat final
Bracket ini dibuat untuk nomor tunggal, dan setiap slot berisi satu nama pemain. Empat pasangan perempat final ditulis langsung di kode, lalu state bracket menyimpan pemenang setiap babak, yang awalnya masih kosong.
Komponen BadmintonBracket, pasangan perempat final:
const QUARTER_PAIRINGS: [string, string][] = [
["Andi Wijaya", "Budi Santoso"],
["Citra Dewi", "Dedi Pratama"],
["Eka Putri", "Fajar Nugroho"],
["Gita Rahma", "Hadi Susilo"],
];
interface BracketState {
quarterWinners: (string | null)[];
semiWinners: (string | null)[];
finalWinner: string | null;
}
const initialState: BracketState = {
quarterWinners: [null, null, null, null],
semiWinners: [null, null],
finalWinner: null,
};
Langkah 2: susun semifinal dan final dari pemenang
Pasangan semifinal dan final tidak ditulis di kode, karena keduanya baru terbentuk setelah pemenang babak sebelumnya dipilih. Semifinal pertama mempertemukan pemenang perempat final pertama dan kedua, semifinal kedua mempertemukan pemenang ketiga dan keempat, sedangkan final terisi setelah kedua semifinal punya pemenang.
Komponen BadmintonBracket, pasangan semifinal dan final dari pemenang:
const semiPairings = useMemo((): ([string, string] | null)[] => {
const w = state.quarterWinners;
const p0 =
w[0] && w[1] ? ([w[0], w[1]] as [string, string]) : null;
const p1 =
w[2] && w[3] ? ([w[2], w[3]] as [string, string]) : null;
return [p0, p1];
}, [state.quarterWinners]);
const finalPairing = useMemo((): [string, string] | null => {
const [a, b] = state.semiWinners;
return a && b ? [a, b] : null;
}, [state.semiWinners]);
Langkah 3: tampilkan bracket di halaman
Setiap babak tampil sebagai satu kolom di layar lebar, sehingga ketiga babak berjajar dari kiri ke kanan, sedangkan di layar yang lebih kecil ketiganya tersusun ke bawah dalam satu kolom. Slot di semifinal dan final tampil sebagai tempat kosong sampai pemenang dari babak sebelumnya dipilih.
Komponen BadmintonBracket, tampilan tiga babak:
<div className="grid grid-cols-1 gap-8 lg:grid-cols-3 lg:gap-6">
<Round title="Perempat Final">
{QUARTER_PAIRINGS.map((pair, i) => (
<MatchBox
key={i}
pair={pair}
selected={state.quarterWinners[i]}
onPick={(player) => pickQuarter(i, player)}
/>
))}
</Round>
<Round title="Semifinal">
{semiPairings.map((pair, i) =>
pair ? (
<MatchBox
key={i}
pair={pair}
selected={state.semiWinners[i]}
onPick={(player) => pickSemi(i, player)}
/>
) : (
<Placeholder key={i} />
),
)}
</Round>
{/* … Final … */}
</div>

Langkah 4: pilih pemenang dan naikkan ke babak berikutnya
Pemenang dipilih dengan mengetuk nama pemain, dan yang disimpan hanya nama pemenang, tanpa skor. Semua pilihan itu ada di state React di browser, jadi hasilnya hilang setelah halaman di-reload. Kalau salah pilih, pemenang bisa dikoreksi dengan mengetuk pemain lain di pertandingan yang sama, tetapi babak setelahnya ikut dikosongkan dan perlu dipilih ulang, sedangkan tombol “Reset bracket” mengosongkan semuanya.
Komponen BadmintonBracket, memilih pemenang dan reset:
const pickQuarter = useCallback((matchIndex: number, player: string) => {
setState((prev) => ({
quarterWinners: prev.quarterWinners.map((w, i) =>
i === matchIndex ? player : w,
),
semiWinners: [null, null],
finalWinner: null,
}));
}, []);
const pickSemi = useCallback((matchIndex: number, player: string) => {
setState((prev) => ({
...prev,
semiWinners: prev.semiWinners.map((w, i) =>
i === matchIndex ? player : w,
),
finalWinner: null,
}));
}, []);
const pickFinal = useCallback((player: string) => {
setState((prev) => ({ ...prev, finalWinner: player }));
}, []);
const reset = () => setState(initialState);

Langkah 5: rencana untuk turnamen sungguhan
Karena hasil di demo hanya tersimpan di browser, turnamen sungguhan membutuhkan dua tambahan, yaitu database untuk menyimpan pemenang supaya hasilnya tidak hilang setelah reload, dan hosting supaya panitia cukup membagikan satu link. Kalau kamu ingin memakai server sendiri, langkahnya ada di deploy Next.js di VPS murah.
Pertanyaan yang sering ditanyakan
Apa bedanya bracket sistem gugur dan klasemen setengah kompetisi?
Di sistem gugur, pemain yang kalah langsung tersingkir, sehingga 8 pemain hanya butuh tujuh pertandingan sampai juara ditentukan, seperti di demo ini. Di setengah kompetisi (round robin), setiap pemain bertemu semua pemain lain satu kali, jadi 8 pemain membutuhkan 28 pertandingan dan tidak ada yang tersingkir di tengah jalan. Karena bentuknya berbeda, tampilannya juga berbeda, yaitu bracket untuk sistem gugur dan tabel hasil untuk setengah kompetisi, dan yang dibangun di tutorial ini hanya sistem gugur.
Bagaimana cara mengatur bye kalau jumlah pemain ganjil?
Demo di tutorial ini belum menangani bye, karena ukurannya tetap 8 pemain dengan pasangan yang ditulis langsung di kode. Untuk jumlah pemain lain, kamu perlu fungsi yang membulatkan jumlah slot ke 2, 4, 8, 16 atau 32, mengisi slot kosong sebagai bye, lalu meloloskan lawan bye ke babak berikutnya secara otomatis.
Apakah bracket ini bisa dipakai untuk ganda dan beregu?
Bracket di tutorial ini dibuat untuk tunggal, tetapi untuk ganda kamu cukup menulis nama pasangan di slot, misalnya “Pemain A/Pemain B”. Untuk beregu, satu pertemuan berisi beberapa partai, jadi data dan tampilannya perlu diubah supaya bisa mencatat partai-partai itu dan menentukan regu yang menang. Fitur beregu ini belum ada di demo.
Bisakah pemenang dikoreksi setelah naik ke babak berikutnya?
Bisa, seperti dijelaskan di Langkah 4, dengan aturan pengosongan berikut:
- Pemenang perempat final yang diganti mengosongkan kedua semifinal dan final, termasuk semifinal di paruh bracket yang lain, karena fungsi
pickQuartermengisi ulangsemiWinnersdanfinalWinnerdengannull. - Pemenang semifinal yang diganti hanya mengosongkan final.
- Pemenang final bisa diganti bebas tanpa mengosongkan apa pun.
- Ketukan kedua pada pemenang yang sama juga mengosongkan babak setelahnya.
Penutup
Bracket di website sendiri paling berguna saat hasil turnamen klub perlu bisa dibuka lagi di satu URL milik klub. Catat dulu turnamen apa saja yang akan klub kamu gelar tahun ini. Kalau kamu ingin bracket seperti ini dibuatkan sekaligus dengan scoreboard live dengan Next.js, kirim daftar turnamen itu lewat jasa pembuatan website Next.js, dan saya balas dalam 24 jam. Kamu juga bisa melihat dulu layanan Next.js development saya.
Terakhir dicek: 18 Oktober 2026.
Need this built instead of read about?
Send me your project. I reply within 24 hours and tell you plainly whether I’m the right fit.
Send me your project

