Journey
CatatanSEOCloudflare404

Sebuah 404 sungguhan, bukan 200 yang semu

Situsnya tayang, cepat, dan aman. Lalu kami mengetik satu URL yang sengaja salah ke browser — pangaea.id/halaman-yang-tak-pernah-ada — dan situs kami dengan tenang menyajikan halaman 404 yang cantik. Masalahnya: ia menyajikannya dengan status HTTP 200 OK.

Bagi pengunjung, perbedaannya tak terlihat. Bagi Google, itu kebohongan — dan Google menghukum kebohongan itu.

Kenapa situs statis berbohong "OK"

Cloudflare Pages punya satu asumsi yang masuk akal: kalau sebuah project tak punya 404.html di level atas, ia menganggap Anda membangun single-page app. Maka untuk setiap URL yang tak cocok dengan file mana pun, ia menyajikan index.html — shell aplikasi — dengan status 200, dan membiarkan JavaScript di sisi klien yang memutuskan halaman apa yang muncul.

Itu sempurna untuk SPA klasik. Tapi situs kami di-prerender: tiap halaman adalah file. Jadi ketika Pages menyajikan shell di 200 untuk URL yang tak ada, hasilnya adalah yang oleh para SEO disebut soft 404 — halaman yang berkata "tidak ditemukan" ke mata manusia, tapi berkata "semua baik" ke crawler.

Kenapa itu buruk? Google secara eksplisit memperlakukan soft 404 sebagai cacat kualitas. Halaman "kosong" itu bisa terindeks, mengencerkan otoritas situs, memboroskan crawl budget, dan muncul di hasil pencarian sebagai pintu menuju ketiadaan. Status yang benar untuk halaman yang tak ada adalah satu, dan hanya satu: 404.

Perbaikannya: biarkan Cloudflare berjalan menaiki pohon

Ada satu kalimat di dokumentasi Cloudflare Pages yang membuka semuanya:

Jika sebuah file tak ditemukan, ia akan terus menelusuri pohon direktori ke atas untuk mencari file 404.html yang cocok, berakhir di /404.html.

Dokumentasi Cloudflare Pages

Jadi kuncinya bukan menulis kode — tapi menaruh satu file di tempat yang tepat. Kalau ada dist/404.html, Cloudflare berhenti menebak SPA: untuk URL tak dikenal apa pun, ia menyajikan file itu dengan status 404 sungguhan. Berjalan menaiki direktori dari path yang diminta sampai ketemu.

Karena situs kami dwibahasa, kami butuh dua file, bukan satu:

/id/halaman-ngawurcari dist/id/404.htmlketemu → 404 Indonesia
Untuk path Indonesia, Cloudflare menemukan dist/id/404.html lebih dulu — sebelum sampai ke dist/404.html — jadi pengunjung Indonesia mendapat 404 dalam bahasanya.

Berjalan menaiki pohon, /id/<ngawur> menemui dist/id/404.html (404 Indonesia) sebelum sampai ke dist/404.html (404 Inggris di root). Itulah sebabnya kami merilis keduanya: bukan kelebihan, tapi syarat agar pengunjung Indonesia tak terdampar di halaman 404 berbahasa Inggris.

Merangkainya tanpa membuat file dengan tangan

Kami tak suka aset yang ditulis tangan — keduanya akan ketinggalan zaman saat desainnya berubah. Jadi 404-nya adalah halaman React sungguhan (/404 + /id/404), di-prerender seperti halaman lain. Lalu sebuah script pasca-build, gen-404.mjs, menyalin keduanya ke tempat yang dicari Cloudflare:

Yang di-prerender SSG

  • dist/404/index.html
  • dist/id/404/index.html
  • Bagus untuk navigasi di dalam app, tapi bukan yang dilihat Cloudflare saat menelusuri pohon

Yang disalin gen-404.mjs

  • dist/404.html
  • dist/id/404.html
  • File inilah yang disajikan Cloudflare dengan status 404 untuk URL tak dikenal

dirStyle: 'nested' merender rute jadi 404/index.html; gen-404 mengangkatnya satu tingkat jadi 404.html. Script berjalan sebelum csp-hash.mjs, jadi script bootstrap inline di halaman yang disalin ikut ter-hash ke CSP.

Dua jebakan yang sempat menggigit

Halaman 404 harus tak terindeks — ia bukan tujuan nyata. Tapi index.html kami dulu membawa satu tag robots index, follow, dan halaman per-rute menambahkan miliknya sendiri. Untuk 404, itu berarti dua arahan yang saling bertentangan: index dan noindex di halaman yang sama. Kami pindahkan tag robots keluar dari index.html ke PageHead, jadi tiap halaman memancarkan tepat satuindex untuk halaman nyata, noindex, follow untuk 404 — tanpa kontradiksi. Kami juga melepaskan 404 dari canonical, hreflang, dan sitemap.xml: halaman yang tak ditemukan tak boleh mengklaim URL kanonik atau alternatif lokal.

Buktikan secara lokal

vite preview tak tahu trik nearest-404.html Cloudflare, jadi kami ajari server preview kami sendiri (serve-dist.mjs) untuk meniru perilaku itu: untuk URL tak dikenal ia berjalan menaiki pohon mencari 404.html terdekat dan menyajikannya dengan status 404. Sekarang sekali npm run build, preview mengembalikan 404 untuk path Inggris yang salah, 404 untuk path Indonesia yang salah (dalam bahasa Indonesia), dan 200 untuk halaman nyata — persis seperti edge.

Lakukan

  • Rilis sebuah 404.html di level atas (per locale) agar host statis mengembalikan status yang benar
  • Beri 404 noindex dan keluarkan dari sitemap, canonical, dan hreflang
  • Pastikan tepat satu arahan robots per halaman
  • Buktikan status code secara lokal, jangan asal percaya halamannya terlihat benar

Jangan

  • Mengandalkan halaman 404 cantik yang disajikan di 200 — itu soft 404, dan Google menghukumnya
  • Membungkus tag head di dalam fragment dengan react-helmet — tagnya akan hilang diam-diam
  • Mengindeks halaman not-found atau mendaftarkannya di sitemap

Inti ceritanya

Status code adalah cara situs Anda mengatakan kebenaran ke mesin. Halaman 404 yang cantik tak ada artinya kalau ia berbohong "200 OK" di header. Penjelasan teknis lengkapnya — mekanisme soft 404, penelusuran pohon nearest-404.html, dan rangkaian SSG-nya — ada di artikel baru kami: Kenapa situs statis mengembalikan 200 untuk halaman yang hilang →.

Ini menutup rangkaian go-live: domain diarahkan, situs dirilis, satu alamat kanonik, cepat dan aman — dan sekarang ia mengatakan kebenaran bahkan saat Anda meminta halaman yang tak ada.

Sources

  1. Cloudflare Pages — perilaku Not Found (nearest 404.html)
  2. Google Search — soft 404