Peta URL lama & redirect 301
Ini bagian migrasi yang paling langsung berdampak ke trafik, dan yang kerusakannya tidak muncul sebagai error di mana pun — hanya sebagai grafik yang menurun.
Intisari
- Kumpulkan URL dari empat sumber: Search Console, log akses, sitemap lama, dan database.
- 301 untuk pindah permanen. 302 memberitahu Google URL lamanya masih yang utama.
- Peta 40 ribu URL masuk ke database, dilayani rute rest — bukan ke konfigurasi.
- Rantai redirect memboroskan anggaran perayapan. Petakan langsung ke tujuan akhir.
- Verifikasi dengan menjalankan seluruh daftar URL lewat
curldan memeriksa status serta tujuannya.
Kumpulkan URL dari empat sumber
# 1. Log akses nginx/Apache CI3 — sumber paling lengkap
zcat /var/log/nginx/access.log-*.gz \
| awk '$9 == 200 && $6 == "\"GET" {print $7}' \
| sed 's/?.*//' | sort | uniq -c | sort -rn > /tmp/url-log.txt
wc -l /tmp/url-log.txt
# 2. Sitemap lama
curl -s https://portal.contoh.id/sitemap.xml \
| grep -oP '(?<=<loc>)[^<]+' > /tmp/url-sitemap.txt
# 3. Search Console → Ekspor "Pages" (URL yang benar-benar terindeks)
# 4. Database — bentuk URL yang dihasilkan CI3
mysql -N -e "SELECT CONCAT('/news/detail/', id, '/', slug) FROM artikel WHERE status='terbit'" portal \
> /tmp/url-db.txt
Log akses adalah sumber yang paling sering dilupakan dan paling berharga. Ia memuat URL yang benar-benar dikunjungi manusia — termasuk bentuk URL lama dari sistem sebelum CI3, tautan dari media sosial dengan parameter aneh, dan halaman yang tidak ada di sitemap mana pun. Kolom jumlah kunjungan juga memberitahumu mana yang benar-benar penting.
Pola URL CI3 dan padanannya
| URL lama | URL baru | Cara memetakan |
|---|---|---|
/news/detail/4471/rupiah-menguat | /berita/rupiah-menguat | ID → slug, dari database |
/news/detail/4471 | /berita/rupiah-menguat | Sama |
/kategori/ekonomi | /berita/ekonomi | Pola |
/index.php/news/detail/4471 | Sama | Buang /index.php dulu |
/emiten.php?kode=BBCA | /perusahaan/BBCA | Query → jalur |
/berita.html?p=12 | /berita?hal=12 | Sistem sebelum CI3 |
Tabel peta
CREATE TABLE peta_url (
jalur_lama VARCHAR(500) NOT NULL,
jalur_baru VARCHAR(500) NOT NULL,
status SMALLINT NOT NULL DEFAULT 301,
hit INT UNSIGNED NOT NULL DEFAULT 0,
terakhir DATETIME NULL,
PRIMARY KEY (jalur_lama(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Isi dari database untuk pola yang bisa diturunkan
INSERT IGNORE INTO peta_url (jalur_lama, jalur_baru)
SELECT CONCAT('/news/detail/', id, '/', slug), CONCAT('/berita/', slug)
FROM artikel WHERE status = 'terbit';
INSERT IGNORE INTO peta_url (jalur_lama, jalur_baru)
SELECT CONCAT('/news/detail/', id), CONCAT('/berita/', slug)
FROM artikel WHERE status = 'terbit';
Melayaninya
---
// src/pages/[...jalur].astro — penangkap terakhir
export const prerender = false;
import { cariPetaUrl } from "@/lib/peta-url";
const jalur = "/" + (Astro.params.jalur ?? "");
// 1. Normalisasi bentuk yang umum
const dinormalkan = jalur
.replace(/^\/index\.php/, "")
.replace(/\.html$/, "")
.replace(/\/+$/, "") || "/";
// 2. Cari di peta
const tujuan = await cariPetaUrl(dinormalkan);
if (tujuan) {
return Astro.redirect(tujuan.jalurBaru, tujuan.status);
}
// 3. Coba pola yang bisa dihitung, tanpa kueri peta
const cocok = dinormalkan.match(/^\/news\/detail\/(\d+)/);
if (cocok) {
const slug = await slugArtikelById(Number(cocok[1]));
if (slug) return Astro.redirect(`/berita/${slug}`, 301);
}
// 4. Benar-benar tidak ada
Astro.response.status = 404;
Astro.response.headers.set("Cache-Control", "public, max-age=0, s-maxage=300");
---
<Halaman404 jalurDiminta={jalur} />
Rute rest ini otomatis kalah dari semua rute lain (Fase 1). Kamu tidak perlu mengatur urutan
apa pun — /berita/[slug].astro selalu menang, dan penangkap ini hanya menerima yang tidak
cocok dengan rute mana pun. Ini kenapa aturan prioritas routing Astro sangat cocok untuk pekerjaan ini.
Cache-nya, karena crawler akan datang berulang
// src/lib/peta-url.ts
import { cache } from "./cache-memori";
export function cariPetaUrl(jalur: string) {
return cache(`peta:${jalur}`, 3600, async () => {
const b = await dbBaca
.selectFrom("peta_url")
.select(["jalur_baru", "status"])
.where("jalur_lama", "=", jalur)
.executeTakeFirst();
// Catat pemakaian, tanpa ditunggu
if (b) {
void dbTulis.updateTable("peta_url")
.set((eb) => ({ hit: eb("hit", "+", 1), terakhir: new Date() }))
.where("jalur_lama", "=", jalur).execute();
}
return b ? { jalurBaru: b.jalur_baru, status: b.status } : null;
});
}
Redirect-nya sendiri juga layak di-cache di Cloudflare:
Astro.response.headers.set("Cache-Control", "public, max-age=0, s-maxage=86400");
Redirect 301 tidak berubah. Meng-cache-nya sehari di edge berarti crawler yang merayapi 40 ribu URL lama tidak memukul origin-mu 40 ribu kali.
Jangan bikin rantai
BURUK:
/news/detail/4471 → 301 → /berita/detail/4471 → 301 → /berita/rupiah-menguat
BAIK:
/news/detail/4471 → 301 → /berita/rupiah-menguat
Setiap lompatan memakan anggaran perayapan dan menambah latensi bagi pembaca. Kalau kamu pernah mengubah bentuk URL sebelumnya, ratakan seluruh rantai lama langsung ke tujuan akhir sekarang.
301 vs 302 vs 410
| Status | Artinya bagi Google | Pakai untuk |
|---|---|---|
| 301 | Pindah permanen; indeks & sinyal peringkat dipindahkan | Migrasi URL |
| 302 / 307 | Sementara; URL lama tetap yang utama | Login, A/B test |
| 404 | Tidak ada; mungkin kembali | Salah ketik URL |
| 410 | Sengaja dihapus, tidak akan kembali | Artikel yang ditarik permanen |
Memakai 302 untuk migrasi adalah kesalahan yang merugikan berbulan-bulan. Google akan mempertahankan URL lama di indeks dan tidak memindahkan sinyal peringkat ke URL baru. Trafiknya tidak hilang seketika — ia perlahan menurun, dan penyebabnya sulit ditemukan karena semuanya "bekerja".
Verifikasi seluruh daftar
#!/usr/bin/env bash
# uji-redirect.sh — jalankan terhadap SELURUH daftar URL lama
U=https://portal.contoh.id
gagal=0
while read -r jalur; do
hasil=$(curl -sI -o /dev/null -w '%{http_code} %{redirect_url}' "$U$jalur")
kode=${hasil%% *}
tujuan=${hasil#* }
case "$kode" in
301) ;; # baik
200) echo "PERINGATAN 200 (tidak dialihkan): $jalur" ;;
404) echo "GAGAL 404: $jalur"; gagal=$((gagal+1)) ;;
*) echo "GAGAL $kode: $jalur"; gagal=$((gagal+1)) ;;
esac
# Deteksi rantai
if [ -n "$tujuan" ]; then
k2=$(curl -sI -o /dev/null -w '%{http_code}' "$tujuan")
[ "$k2" = "301" ] && echo "RANTAI: $jalur → $tujuan → …"
fi
done < /tmp/url-semua.txt
echo "Gagal: $gagal"
exit $((gagal > 0))
Setelah peluncuran
| Waktu | Yang dilakukan |
|---|---|
| Hari 0 | Kirim sitemap baru di Search Console; pertahankan yang lama sementara |
| Minggu 1 | Pantau laporan Pages harian; perbaiki 404 yang muncul |
| Minggu 2–4 | Trafik biasanya turun sementara lalu pulih — ini normal |
| Bulan 2 | Kueri peta_url untuk baris ber-hit nol; itu URL yang tidak pernah dipakai |
| Bulan 6 | Redirect tetap dipertahankan — jangan dihapus |
Jangan pernah menghapus redirect. Biayanya satu baris database dan satu kueri ter-cache; keuntungannya adalah tautan dari artikel lain, dari media sosial, dan dari arsip yang dibuat bertahun-tahun lalu tetap hidup. Portal berita hidup dari tautan masuk, dan tautan yang mati tidak pernah kembali.
Latihan: kumpulkan URL dari keempat sumber untuk portalmu dan gabungkan jadi satu daftar unik.
Hitung jumlahnya — angka itu menentukan apakah peta di database benar-benar perlu atau
redirects di konfigurasi sudah cukup. Bangun tabel peta_url, isi dari
database, dan jalankan skrip verifikasi terhadap 500 URL paling sering dikunjungi dari log-mu.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.