Catatan dari produksi
CloudflareSEO404

Soft 404: bikin Cloudflare Pages kembalikan 404 asli

Ketik sebuah URL yang tak ada di situs statis dan Anda sering mendapat sesuatu yang ganjil: halaman yang terlihat seperti "tidak ditemukan", tapi yang dilaporkan server sebagai 200 OK — sukses. Bagi mata Anda tak ada bedanya. Bagi Google, itu cacat — namanya soft 404, dan ada hukumannya.

Artikel ini menjelaskan kenapa itu terjadi, lalu menunjukkan cara membuat Cloudflare Pages mengembalikan 404 sungguhan untuk halaman yang hilang — termasuk trik untuk situs dwibahasa.

Apa itu status code, dalam satu menit

Tiap kali browser meminta halaman, server membalas dengan status code — sebuah angka yang merangkum hasilnya, sebelum konten apa pun. Tiga yang penting di sini:

  • 200 OK — "Ini halaman yang Anda minta."
  • 301 Moved Permanently — "Halaman itu kini ada di alamat lain."
  • 404 Not Found — "Halaman itu tak ada."

Pengunjung jarang melihat angka ini, tapi crawler — Googlebot, Bingbot, ClaudeBot — bertindak berdasarkannya. Status code adalah cara situs Anda mengatakan kebenaran ke mesin.

Kenapa situs statis mengembalikan 200 untuk halaman yang hilang

Inilah jebakannya. Sebuah host statis seperti Cloudflare Pages mencocokkan tiap URL dengan sebuah file. Minta /pricing, ia menyajikan pricing/index.html. Tapi minta /halaman-ngawur, tak ada file yang cocok — lalu apa?

Cloudflare Pages punya satu asumsi: kalau project Anda tak punya 404.html di level atas, ia menganggap Anda membangun single-page app (SPA). Dalam SPA klasik, server harus menyajikan satu index.html untuk tiap path dan membiarkan JavaScript yang merutekan — jadi Pages dengan patuh menyajikan index.html di 200 untuk path tak dikenal apa pun.

Untuk SPA sungguhan itu benar. Untuk situs prerendered seperti ini — tiap halaman adalah file sungguhan — itu salah. Pengunjung melihat halaman 404 (router sisi-klien merendernya setelah memuat), tapi header sudah mengatakan 200 OK. Itulah soft 404: satu sinyal untuk manusia, sinyal lain untuk mesin.

Kenapa Google peduli? Dokumentasinya menyebut soft 404 sebagai masalah kualitas: halaman "kosong" itu bisa terindeks, memboroskan crawl budget, mengencerkan otoritas situs, dan muncul di hasil pencarian sebagai pintu menuju ketiadaan. Status yang benar untuk halaman yang tak ada adalah 404 — selalu.

Perbaikannya: nearest-404.html dan penelusuran pohon

Satu kalimat di dokumentasi Cloudflare Pages adalah seluruh perbaikannya:

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

Terjemahannya: taruh sebuah 404.html, dan Cloudflare berhenti menebak SPA. Untuk URL tak dikenal apa pun, ia naik menelusuri direktori dari path yang diminta sampai menemukan 404.html — lalu menyajikannya dengan status 404 sungguhan. Tak ada lagi soft 200.

GET /id/halaman-ngawurtak cocok dengan file mana puncari dist/id/404.htmlada → sajikan · 404 Indonesianaik: dist/404.htmlcadangan (404 Inggris di root)Path Inggris tak dikenal langsung mengenai dist/404.html →HTTP 404status sungguhan — bukan soft 200
Untuk path Indonesia, Cloudflare menemukan dist/id/404.html lebih dulu — jadi pengunjung Indonesia mendapat 404 dalam bahasanya — lalu naik ke dist/404.html sebagai cadangan.

Kenapa dua file, bukan satu

Situs ini dwibahasa: Inggris di /, Indonesia di /id/*. Penelusuran pohon menemukan 404.html terdalam yang cocok lebih dulu, jadi kami merilis dua:

  • dist/404.html — menangkap /halaman-ngawur (404 Inggris).
  • dist/id/404.html — menangkap /id/halaman-ngawur (404 Indonesia).

Tanpa file Indonesia, /id/<ngawur> akan naik melewati /id/ dan mengenai dist/404.html — pengunjung Indonesia terdampar di halaman not-found berbahasa Inggris. File Indonesia menangkapnya lebih dulu. Statusnya tetap 404; yang berubah hanya bahasanya.

Merangkainya di SSG

Kami tak menulis kedua file HTML itu dengan tangan — keduanya akan ketinggalan zaman saat desain berubah. Alurnya:

1. 404 adalah halaman sungguhan, di-prerender. Ada rute untuk /404 dan /id/404, sama seperti halaman lain, jadi keduanya ter-prerender ke HTML statis penuh.

2. dirStyle: 'nested' merendernya bersarang. vite-react-ssg menulis rute sebagai 404/index.html, bukan 404.html:

dist/404/index.html        ← yang di-prerender SSG
dist/id/404/index.html

Bagus untuk navigasi di dalam app — tapi bukan tempat yang dicari Cloudflare saat menelusuri pohon.

3. Sebuah script pasca-build mengangkatnya satu tingkat. gen-404.mjs menyalin tiap halaman bersarang ke level-atas yang dibaca Cloudflare:

dist/404/index.html     →  dist/404.html      (menyajikan /<ngawur>)
dist/id/404/index.html  →  dist/id/404.html   (menyajikan /id/<ngawur>)

Script ini berjalan sebelum csp-hash.mjs, jadi script bootstrap inline di halaman yang disalin ikut ter-hash ke Content-Security-Policy — tak ada celah CSP yang menganga di halaman 404.

Bersihkan sinyal SEO-nya

Halaman 404 bukan tujuan nyata, jadi sinyalnya harus berkata begitu:

  • noindex. 404 memancarkan <meta name="robots" content="noindex, follow"> — jangan indeks, tapi ikuti link kembalinya.
  • Tanpa canonical, tanpa hreflang. Halaman yang tak ditemukan tak boleh mengklaim URL kanonik atau alternatif lokal. Keduanya dilewati di 404.
  • Dikecualikan dari sitemap.xml. Sitemap mendaftarkan halaman yang ingin Anda indeks; 404 bukan salah satunya.

Satu jebakan terakhir, untuk engineer: saat membuat tag robots bersyarat, jangan bungkus tag-tag head di dalam JSX fragment. react-helmet (yang dipakai vite-react-ssg) membuang tag head yang bersarang di dalam fragment — tagnya hilang dari HTML diam-diam. Ratakan jadi child bersyarat — tiap tag jadi child langsung dari <Head>.

Buktikan secara lokal

vite preview tak meniru penelusuran nearest-404.html Cloudflare, jadi server preview kami sendiri melakukannya. Setelah build, ia berjalan menaiki pohon untuk URL tak dikenal dan menyajikan 404.html terdekat dengan status 404:

npm run build
npm run preview:headers -w @pangaea/labs   # menyajikan dist/ seperti edge

curl -I http://localhost:8788/halaman-ngawur      # → HTTP 404 (Inggris)
curl -I http://localhost:8788/id/halaman-ngawur   # → HTTP 404 (Indonesia)
curl -I http://localhost:8788/pricing             # → HTTP 200

Kalau ketiganya benar secara lokal, edge akan setuju. Jangan asal percaya halamannya terlihat benar — cek status code-nya.

Intinya

Situs statis menyajikan file, dan satu file yang hilang gampang berakhir di shell aplikasi pada 200 — sebuah soft 404 yang dihukum Google. Perbaikannya bukan kode, tapi sebuah file di tempat yang tepat: rilis 404.html level-atas (satu per locale), biarkan Cloudflare menelusuri pohon, dan beri halaman itu noindex plus tempatnya di luar sitemap. Sekarang situs mengatakan kebenaran — bahkan saat Anda meminta halaman yang tak ada.

Cerita di balik perbaikan ini ada di entri diary Sebuah 404 sungguhan, bukan 200 yang semu →. Ia masuk dalam rangkaian go-live yang sama dengan SSL & keamanan di edge, CI/CD: GitHub Actions → Cloudflare Pages, dan Root → www: redirect 301.

Sources

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