Registry Dulu, Redirect Kemudian: Pindah URL ke Pohon /videos
Migrasi URL video ke pohon /videos di Next.js App Router dengan registry kategori dan permanentRedirect 308, plus verifikasi curl.
Ringkasan
Jadi semua URL video diganti dari video/slug jadi videos/bidang/kategori/slug biar sesuai hierarki kontennya. Map redirect statis di next.config.js dibatalkan karena ribet dirawat, diganti registry kategori plus permanentRedirect() yang kirim 308 permanen. Sitemap dan tautan internal ikut diubah, tinggal verifikasi pakai curl sampai statusnya 308.
# Pindah dari /video/[slug] ke Pohon /videos: Registry Dulu, Redirect Kemudian
Waktu ekspor sitemap KotaPortal pertama kali dipisah per bidang, sekitar 300 URL video tampil sebagai satu daftar datar. Semuanya berbentuk video/[slug], tidak ada satu pun yang membawa informasi bidang atau kategori. Tim konten mengeluh bahwa halaman hub tiap bidang tidak bisa menautkan koleksi videonya sendiri. Dugaan awalnya kolom di admin panel yang kurang, padahal yang hilang ada di routing: struktur URL-nya sendiri tidak mewakili hierarki konten.
Target barunya jelas sejak awal, yaitu /videos/[bidang]/[kategori]/[slug], dengan /videos sebagai hub. Sisanya tugas memindahkan URL lama tanpa kehilangan sinyal yang sudah terkumpul di mesin pencari.
Salah tebak: map redirect per-URL di next.config.js
Insting pertama adalah menulis daftar pasangan lama-baru di redirects() dalam next.config.js. Untuk puluhan URL, cara itu masuk akal. Untuk ratusan URL video yang bidang dan kategorinya berasal dari API konten, map statis tersebut menjadi beban: setiap kali redaksi memindahkan sebuah video ke kategori lain, satu entri harus diperbarui dengan tangan.
Ditambah lagi, redirects() di next.config.js bekerja berbasis path sebelum render, sementara target tujuan di sini baru diketahui setelah data video diambil [3]. Pilihan itu dibatalkan. Redirect harus ditempatkan di titik yang bisa membaca data.
Registry kategori dan permanentRedirect() di halaman lama
Yang dibangun adalah satu registry kategori di lib/videos.ts, menjadi satu-satunya sumber kebenaran soal slug bidang, label, dan daftar kategori yang valid.
// lib/videos.ts
export const videoModules = {
"dinas-pendidikan": {
label: "Dinas Pendidikan",
kategori: ["prestasi", "beasiswa", "pengumuman"],
},
"dinas-kesehatan": {
label: "Dinas Kesehatan",
kategori: ["imunisasi", "gizi", "layanan"],
},
} as const;
export function isKnownKategori(bidang: string, kategori: string) {
const mod = videoModules[bidang as keyof typeof videoModules];
return !!mod && (mod.kategori as readonly string[]).includes(kategori);
}Halaman lama di app/video/[slug]/page.tsx kemudian memanggil permanentRedirect() dari next/navigation setelah target divalidasi terhadap registry.
// app/video/[slug]/page.tsx (Server Component)
import { permanentRedirect } from "next/navigation";
import { resolveVideoTarget, isKnownKategori } from "@/lib/videos";
export default async function LegacyVideoPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const target = await resolveVideoTarget(slug); // slug baru dari data API
if (!isKnownKategori(target.bidang, target.kategori)) {
throw new Error(`kategori tidak valid: ${target.kategori}`);
}
permanentRedirect(
`/videos/${target.bidang}/${target.kategori}/${target.slug}`,
);
}Tiga detail dari dokumentasi resmi yang membuat pola ini cocok. Fungsi permanentRedirect() bekerja di Server Components, Client Components, Route Handlers, dan Server Functions, serta di luar Server Actions ia menghasilkan respons HTTP 308 Permanent [1]. Fungsi ini melempar NEXT_REDIRECT yang menghentikan render segmen tersebut, sehingga tidak perlu return dan TypeScript menandainya sebagai never [1]. Dibandingkan redirect() yang menghasilkan 307, hanya permanentRedirect() yang menyatakan perpindahan permanen; dokumentasi Next.js sendiri menyarankan memilihnya ketika perpindahan sudah pasti dan tidak akan dikembalikan [3].
Sisi lain yang ikut beres: tidak ada lagi map yang harus dirawat. Redaksi mengganti kategori sebuah video, registry yang menentukan URL baru, dan halaman lama cukuh dengan satu fungsi.
Sitemap, tautan internal, dan verifikasi dengan curl
Generator sitemap diubah agar hanya entri video dengan mode tampilan video yang menghasilkan URL pohon baru; URL lama tidak lagi diterbitkan. Tautan internal dari hub bidang, artikel berita, dan halaman statistik diganti ke pola baru dalam commit yang sama, supaya tidak ada tautan yang kembali membangun jalur lama.
Verifikasinya satu baris. Jalankan terhadap URL lama dan baca status beserta header location:
curl -sI https://portal.example.com/video/pengumuman-ppdb-2026Keluaran yang diharapkan mirip seperti ini:
HTTP/2 308
location: /videos/dinas-pendidikan/pengumuman/pengumuman-ppdb-2026Jika muncul 307, berarti yang terpanggil adalah redirect() biasa atau aturan path dari next.config.js, bukan permanentRedirect(). Jika statusnya 200, halaman lama masih melakukan render penuh dan pemanggilan redirect tidak pernah tercapai, biasanya karena slug tidak ditemukan di registry dan jalur error dilewati lebih dulu.
Setelah 308 terkirim
Dari sisi Googlebot, perpindahan permanen adalah sinyal bahwa URL tujuan yang seharusnya dianggap kanonik. Dokumentasi Search Central menyebut redirect permanen membuat URL baru yang muncul di hasil penelusuran, sedangkan redirect sementara mempertahankan URL sumber [2]. Kode 301 dan 308 diperlakukan setara untuk keperluan ini, dan permanentRedirect() memang mengirim 308 [1][2].
Tunggu perayapan ulang, lalu cek laporan indeks untuk memastikan URL lama turun dan URL pohon baru mulai tercatat. Registry tetap disimpan sebagai satu titik perubahan untuk penyesuaian kategori berikutnya.
---
Sources
- [1] https://nextjs.org/docs/app/api-reference/functions/permanentRedirect — permanentRedirect — Next.js API Reference
- [2] https://developers.google.com/search/docs/crawling-indexing/301-redirects — Redirects and Google Search — Google Search Central
- [3] https://nextjs.org/docs/app/building-your-application/routing/redirecting — How to handle redirects in Next.js — Next.js Docs