{"slug":"nextjs-rsc-flight-format-ai-crawler","locale":"id","isFallback":false,"translationAvailable":["en","id"],"translationUrls":{"en":"/api/notes/nextjs-rsc-flight-format-ai-crawler?locale=en","id":"/api/notes/nextjs-rsc-flight-format-ai-crawler?locale=id"},"title":"Kenapa AI Crawler Tidak Bisa Baca Site Next.js App Router","description":"Next.js App Router output-nya RSC Flight payload, bukan HTML biasa. Crawler HTTP fetch seperti Claude web_fetch tidak bisa baca konten. Ini temuan dan solusinya.","date":"2026-03-31","updated":null,"tags":["nextjs","web","debugging","ai","linux"],"content":"\nSaya iseng minta Claude fetch salah satu artikel SnipGeek langsung dari URL-nya. Yang balik cuma title tag. Isi artikel kosong sama sekali.\n\nReaksi pertama saya: pasti ada yang salah di kode saya.\n\n## Diagnosis Pertama: Client-Side Rendering?\n\nDugaan awalnya simpel — mungkin halaman artikel masih client-side rendered. HTML yang dikirim server cuma shell kosong, konten baru muncul setelah JavaScript jalan. Ini kesalahan klasik Next.js kalau `\"use client\"` nyasar ke page component.\n\nSaya minta Antigravity audit seluruh codebase. Hasilnya bersih:\n\n- `[locale]/blog/[slug]/page.tsx` → ✅ Server Component\n- `[locale]/notes/[slug]/page.tsx` → ✅ Server Component\n- MDX dikompilasi server-side via `next-mdx-remote/rsc` → ✅\n- `generateStaticParams` sudah ada → ✅\n\nSemuanya benar. Tapi kalau begitu, kenapa kontennya kosong?\n\n## Diagnosis Kedua: RSC Flight Format\n\nSaya jalankan diagnostic lebih dalam langsung ke URL live:\n\n```bash\ncurl -s https://snipgeek.com/notes/how-to-read-ai-build-failed-logs | grep -i 'article\\|content\\|body\\|prose' | head -20\n```\n\nResponse-nya **101KB** — bukan halaman kosong. Kata kunci `content`, `article`, `prose` semua ditemukan ratusan kali. Tapi waktu dicek lebih dalam, ini yang saya lihat:\n\n```\n{\"className\":\"text-lg text-foreground/80 prose-content\",\"children\":\"$L1d\"}\n```\n\n`$L1d` bukan teks artikel. Itu referensi ke **React Server Component chunk** — format streaming khusus Next.js App Router yang disebut RSC Flight format. Konten artikelnya ada, tapi terkemas dalam payload yang hanya bisa didecode oleh React runtime.\n\nKonfirmasi:\n\n```bash\ncurl -s https://snipgeek.com/notes/how-to-read-ai-build-failed-logs | grep '<p>'\n# Total <p> tags: 0\n# Total <h2> tags: 0\n```\n\nNol tag HTML biasa. Semua konten ada di dalam RSC payload.\n\n## Ini Bukan Bug — Ini Trade-off Arsitektur\n\nPages Router lama menghasilkan HTML murni: `<p>`, `<h2>`, konten terbaca langsung di response. App Router beralih ke RSC Flight — format streaming yang dioptimalkan untuk performa hydration, tapi tidak bisa dibaca tanpa React runtime.\n\nUntuk SEO, ini tidak masalah:\n\n| Crawler | Bisa Baca Konten? | Alasan |\n|---|---|---|\n| Googlebot | ✅ | Headless Chrome, render JS penuh |\n| Bingbot | ✅ | Sama — render JS penuh |\n| AI crawler (GPTBot, ClaudeBot) | ⚠️ | Tergantung — ada yang render JS, ada yang tidak |\n| Claude via `web_fetch` | ❌ | Plain HTTP fetch, tanpa eksekusi JavaScript |\n\nGoogle bisa baca semua artikel. Masalahnya spesifik ke crawler yang hanya mengandalkan plain HTTP tanpa render JavaScript.\n\n## Solusi: API Route untuk Plain JSON\n\nSaya tambah Route Handler di Next.js yang serve konten artikel sebagai plain JSON — tanpa RSC format, tanpa butuh JavaScript:\n\n```\nGET /api/posts/[slug]?locale=en   → JSON artikel bahasa Inggris\nGET /api/posts/[slug]?locale=id   → JSON artikel bahasa Indonesia\nGET /api/notes/[slug]?locale=en   → JSON notes bahasa Inggris\nGET /api/notes/[slug]?locale=id   → JSON notes bahasa Indonesia\n```\n\nBeberapa keputusan yang saya ambil saat implementasi:\n\n- **Locale fallback** — kalau versi `id` belum ada, fallback ke `en` dengan `isFallback: true` di response.\n- **`X-Robots-Tag: noindex`** — supaya API route tidak diindex Google sebagai duplicate dari halaman utama.\n- **`Cache-Control: public, max-age=3600`** — cache response supaya tidak trigger serverless invocation berulang.\n- **`translationUrls`** — field yang berisi URL lengkap untuk tiap locale yang tersedia, berguna untuk tool yang consume API ini.\n\nSetelah deploy, test langsung:\n\n```bash\ncurl -s \"https://snipgeek.com/api/posts/ubuntu-26-04-beta-sudah-bisa-didownload?locale=id\"\n```\n\nResponse:\n\n```json\n{\n  \"slug\": \"ubuntu-26-04-beta-sudah-bisa-didownload\",\n  \"locale\": \"id\",\n  \"isFallback\": false,\n  \"translationAvailable\": [\"en\", \"id\"],\n  \"translationUrls\": {\n    \"en\": \"/api/posts/ubuntu-26-04-beta-sudah-bisa-didownload?locale=en\",\n    \"id\": \"/api/posts/ubuntu-26-04-beta-sudah-bisa-didownload?locale=id\"\n  },\n  \"title\": \"Ubuntu 26.04 Beta Sudah Rilis — Tapi Jangan Buru-Buru Install\",\n  \"description\": \"...\",\n  \"date\": \"2026-03-30\",\n  \"tags\": [\"ubuntu\", \"linux\", \"beta\"],\n  \"content\": \"\\nSaya nunggu beta Ubuntu 26.04 ini sambil setengah semangat...\"\n}\n```\n\nKonten artikel terbaca penuh sebagai plain text. Siapapun — termasuk AI, developer, atau tool apapun — bisa baca isi artikel SnipGeek tanpa butuh browser atau JavaScript.\n\n<Callout variant=\"info\" title=\"Perubahan Aman\">\n  API route ini hidup sepenuhnya di namespace `/api/*` — terpisah dan tidak bisa mengganggu routing halaman yang sudah ada. Ini **pure additive change** — tidak menyentuh apapun yang sebelumnya.\n</Callout>\n\n## Langkah Selanjutnya\n\nYang saya rencanakan berikutnya: implementasi [`llms.txt`](https://llmstxt.org) — standar baru mirip `robots.txt` tapi khusus untuk AI — yang berisi daftar semua URL konten SnipGeek dalam format yang mudah diproses LLM crawler.\n\nKalau kamu juga punya site Next.js App Router dan frustrasi kenapa AI tidak bisa baca kontennya, coba tambah API route plain JSON seperti ini. Hasilnya langsung terasa.\n\n### Referensi\n1. [Next.js Route Handlers — Next.js Docs](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)\n2. [React Server Components — React Docs](https://react.dev/reference/rsc/server-components)\n3. [llms.txt — Emerging Standard for AI Crawlers](https://llmstxt.org)\n"}