Skip to content

Deep-Link Video di Mega Menu: URL, State, dan Preload yang Benar

Adityo Guni Waluyo

Mega panel video butuh tiga lapisan URL: path identitas, query state UI, hash timestamp. State video butuh sync manual ke hash. Preload metadata + poster, bukan auto. Prefetch route matikan auto, pakai hover.

Ringkasan

Gunakan path URL untuk ID video, query buat state panel, dan hash (#t=) buat timestamp video. Set video preload='metadata' plus poster biar hemat bandwidth, prefetch route pakai hover manual bukan auto. Sinkronin manual currentTime ke hash pas seek biar bookmark dan share tetap work.

Saya mengklik video di mega panel, URL berubah, tetapi saat dibuka di tab baru — video yang terbuka berbeda, atau posisi putar kembali ke nol.

Dulu saya pikir cukup menambahkan ?v=videoId ke URL mega menu. Ternyata Vercel Academy menyarankan: identitas menggunakan path segment (/video/[id]), state menggunakan query string (?panel=open), timestamp menggunakan hash (#t=42). Tiga lapisan, bukan satu.

URL memiliki dua tugas

Identitas: resource mana? State: bagaimana ditampilkan? Mencampurnya membuat rute rapuh dan bookmark rusak. Vercel Academy menyarankan pemisahan: path segment untuk ID video, query string untuk state panel (filter, buka/tutup), hash fragment untuk posisi temporal.

https://app.example.com/video/abc123?panel=open#t=1:30

Path video/abc123 = identitas. Query panel=open = state UI. Hash t=1:30 = timestamp media. Browser tidak mengirim hash ke server — cocok untuk client-side seek.

Pemisahan ini juga memengaruhi rendering di Next.js. Dokumentasi useSearchParams menyatakan bahwa route statis tetap dapat dirender statis hingga batas <Suspense> terdekat, sedangkan Vercel Academy menegaskan bahwa searchParams membuat route dirender per request (dinamis). Kedua pernyataan benar untuk API berbeda (hook di client vs prop searchParams di page), tetapi mudah disalahpahami. Pilih satu pendekatan secara sadar: gunakan params untuk identitas yang bisa diprerender, searchParams untuk state yang memang butuh rendering per request.

State restoration: jangan mengandalkan React state

<Activity> React mempertahankan state internal saat hidden→visible. Namun currentTime dan paused pada elemen <video> adalah DOM state, bukan React state. Perlu sinkronisasi manual ke URL.

// Client Component
const videoRef = useRef<HTMLVideoElement>(null);

useEffect(() => {
  const hash = window.location.hash; // #t=90
  const match = hash.match(/#t=([\d:]+)/);
  if (match && videoRef.current) {
    videoRef.current.currentTime = parseTime(match[1]);
  }
}, []);

function parseTime(str: string): number {
  const parts = str.split(':').map(Number);
  return parts.length === 3
    ? parts[0] * 3600 + parts[1] * 60 + parts[2]
    : parts.length === 2
    ? parts[0] * 60 + parts[1]
    : parts[0];
}

Saat user melakukan seek, perbarui hash:

function onTimeUpdate() {
  const t = Math.floor(videoRef.current!.currentTime);
  history.replaceState(null, '', '#t=' + t);
}

Penting: hash (#t=) tidak dikirim ke server, sehingga cocok untuk state client-side seperti posisi putar. Media Fragments URI (MDN) mendefinisikan standar #t=[npt:][mulai][,selesai] untuk file media langsung, tetapi pada halaman web, hash halaman tidak otomatis diteruskan ke elemen <video>. Implementasi referensi (Aaron Parecki, blog pribadi) menggunakan JavaScript untuk menyalin hash halaman ke src video dan memperbarui hash dengan history.replaceState saat pause. Di Next.js, hash perlu dibaca di client (window.location.hash) dan diterapkan lewat video.currentTime.

Preloading: metadata cukup, auto boros

Chrome 64+ default preload="metadata" (dulu auto). Atribut preload hanyalah hint — browser boleh mengabaikannya. Sejak Chrome 64, default-nya metadata (sebelumnya auto). Chrome memaksa metadata di jaringan seluler (2G/3G/4G) dan none saat Data Saver aktif.

Mega panel memiliki banyak video: preload="auto" memboroskan bandwidth. Gunakan preload="metadata" + poster.

<video
  preload="metadata"
  poster="/thumbnails/abc123.jpg"
  src="/videos/abc123.mp4"
/>

preload="metadata" hanya mengambil metadata (dimensi, daftar trek, durasi). Browser boleh mengabaikan atribut ini. web.dev menyarankan untuk banyak video di domain yang sama: set preload="metadata", atau beri poster dan preload="none". Ini paling relevan untuk panel berisi banyak thumbnail video.

Prefetch route: matikan auto, pakai hover

Next.js <Link> memprefetch otomatis saat masuk viewport (awal atau lewat scroll), hanya di production. Opt-out dengan prefetch={false}; dokumentasi menyebut ini berguna untuk daftar link yang besar.

Elemen di dalam <Activity> hidden (display: none) tidak masuk viewport — jadi tidak di-prefetch. Jika ingin prefetch on hover, matikan auto dan panggil manual:

<Link
  href={`/video/${id}`}
  prefetch={false}
  onMouseEnter={() => router.prefetch(`/video/${id}`)}
>
  <VideoCard id={id} />
</Link>

Prefetch hanya mengambil route (payload RSC/JS), bukan file video. Preload video harus dikelola terpisah dengan atribut/link preload. <Link> adalah Client Component dan harus terhidrasi sebelum bisa prefetch; bundle JS besar dapat menunda prefetch.

Aksesibilitas: focus management

Mega menu disclosure navigation + deep-link memerlukan focus management. Saat panel dibuka via hash/router, focus ke video pertama atau tombol tutup. Tidak ada spesifikasi W3C/WAI resmi untuk pola ini spesifik — uji dengan screen reader.

Praktik yang disarankan

Tiga lapisan URL: path untuk identitas, query untuk state UI, hash untuk timestamp media. Satu sinkronisasi manual currentTime ke hash. Satu preload="metadata" + poster. Satu prefetch={false} + hover-prefetch. Hasilnya: bookmarkable, shareable, cacheable, performant.


Sumber

  1. Vercel Academy — URL Params vs SearchParams (2026-10-28)
  2. Next.js Docs — Linking and Navigating (2026-10-28)
  3. web.dev — Fast playback with preload (2026-10-28)
  4. React.dev — Activity reference (2026-10-28)
  5. MDN — Media Fragments URI (2026-10-28)

Artikel terkait