ระบบเอกสารในแอป · Docs site (/docs)
On this page
หน้านี้เล่าว่าหน้า devstack.bid/docs ทำงานอย่างไร เราตั้งโจทย์ไว้สามข้อ: (1) เนื้อหามีแหล่งเดียวคือไฟล์ Markdown ใน docs/ ไม่มีการเขียนซ้ำ (2) ทำงานบน Cloudflare Workers ซึ่ง ไม่มี filesystem ตอน runtime และ (3) ทั้งคนและ AI agent หยิบ Markdown ต้นฉบับไปใช้ได้จริง ไม่ใช่แค่อ่านบนเว็บ
ภาพรวม pipeline#
Mermaid source
flowchart LR
MD["docs/**/*.md<br/>(แหล่งความจริงเดียว)"] --> B["scripts/build-docs.mjs<br/>(prebuild / predev / pretest)"]
B --> J["src/generated/docs-bundle.json<br/>raw + HTML + TOC"]
B --> N["src/generated/docs-nav.json<br/>หมวดและชื่อหน้า"]
B --> S["public/docs-search-index.json<br/>ข้อความรายหัวข้อ"]
D["npm run docs:diagrams<br/>(Playwright + mermaid)"] --> SVG["public/docs-assets/diagrams/*.svg"]
SVG --> B
J --> P["/docs, /docs/[...slug]<br/>prerender ตอน build"]
J --> R["/docs/raw/[...slug]<br/>/docs/<slug>.md"]
J --> L["/llms.txt, /llms-full.txt"]
N --> P
N --> C["command palette"]
S --> Q["ช่องค้นหา (client)"]
ขั้นตอน build#
scripts/build-docs.mjs รันอัตโนมัติก่อน npm run build (ซึ่ง npm run cf:build เรียก), npm run dev, npm test และ npm run typecheck ไฟล์ที่สร้างทั้งหมดอยู่ใน .gitignore จึงไม่มีวันเก่ากว่า docs/
- Parse ด้วย remark-parse + remark-gfm (ตาราง, task list, strikethrough)
- แปลงเป็น HTML ด้วย remark-rehype โดย ทิ้ง raw HTML ในไฟล์ Markdown
- Sanitize ด้วย rehype-sanitize (schema แบบ GitHub) ลิงก์
javascript:และแท็กอันตรายถูกตัดทิ้ง - หลัง sanitize จึงแปลงต่อเฉพาะสิ่งที่เราควบคุมเอง: ใส่
idให้หัวข้อ (rehype-slug ใช้กฎเดียวกับ GitHub จึงรองรับหัวข้อภาษาไทย), ปุ่ม#ข้างหัวข้อ, highlight โค้ดด้วย lowlight, แถบบนบล็อกโค้ดพร้อมปุ่มคัดลอก, ห่อตารางให้เลื่อนแนวนอนได้บนมือถือ - ลิงก์ ระหว่างไฟล์ เช่น
../reference/api-plan.mdถูกแปลงเป็น/docs/reference/api-planส่วนลิงก์ไปไฟล์โค้ด (เช่นsrc/lib/composer.ts) จะเป็นลิงก์ GitHub เมื่อตั้งNEXT_PUBLIC_REPO_URLไว้ ถ้าไม่ตั้งจะแสดงเป็นข้อความธรรมดา - Mermaid ถูกแทนด้วยไฟล์ SVG ที่เรนเดอร์ไว้ล่วงหน้า (ชื่อไฟล์คือ hash ของ source) หน้าเว็บจึงไม่ต้องโหลด JavaScript ของ Mermaid และไม่ต้องแก้ CSP ถ้าแก้ diagram แล้วยังไม่รัน
npm run docs:diagramsบล็อกนั้นจะแสดงเป็นโค้ดแทน build ไม่พัง
Route ที่ได้#
| URL | ได้อะไร |
|---|---|
/docs |
หน้าแรกเอกสาร (จาก docs/README.md) |
/docs/<slug> |
หน้าเอกสาร เช่น /docs/user-guide/getting-started (slug = path ตัวพิมพ์เล็ก ไม่มี .md) |
/docs/<slug>.md, /docs/index.md |
Markdown ต้นฉบับแบบไบต์ต่อไบต์ Content-Type: text/markdown; charset=utf-8 |
/docs/raw/<slug> |
ปลายทางจริงของไฟล์ดิบ (rewrite จาก .md ใน next.config.ts) |
/docs/agents/platform-manifest.json |
manifest แบบ JSON |
/llms.txt |
สารบัญลิงก์ไฟล์ดิบทุกหน้าตามรูปแบบ llmstxt.org |
/llms-full.txt |
ทุกหน้ารวมเป็นไฟล์เดียว |
slug ที่ไม่มีอยู่ได้ HTTP 404 จริง ผ่าน rewrite แบบเดียวกับ /templates/<id> (เพราะ notFound() ใน Cache Components จะ stream หลัง shell ที่เป็น 200) ไฟล์ดิบและ llms*.txt ส่ง Access-Control-Allow-Origin: * เพื่อให้เครื่องมือของคนอื่นดึงไปใช้ได้
UI (Material 3 บนธีมดำของเรา)#
- Navigation drawer ด้านซ้ายแบ่งตามหมวด (user-guide, concepts, architecture, history, operations, contributing, reference, agents) รายการที่เลือกอยู่ใช้สีแบบ "secondary container" และทุกปุ่มมี state layer ตอน hover/focus/กด
- บนมือถือมี top app bar เล็ก ๆ ใต้ header กดปุ่มเมนูเพื่อเปิด drawer แบบ modal จากซ้าย
- ในหน้านี้ (TOC) ด้านขวาบนเดสก์ท็อป ไฮไลต์หัวข้อที่กำลังอ่าน บนจอแคบเป็นกล่องพับได้เหนือเนื้อหา
- breadcrumb, ปุ่มก่อนหน้า/ถัดไป, ปุ่ม คัดลอก Markdown, ดูไฟล์ดิบ และ แก้ไขบน GitHub (ซ่อนไว้จนกว่าจะตั้ง
NEXT_PUBLIC_REPO_URL) - ฟอนต์ Space Grotesk (
next/font/google, self-host) ใช้กับหัวข้อและ UI ของหน้าเอกสารเท่านั้น โหลดในsrc/app/docs/layout.tsxหน้าอื่นจึงไม่ต้องดาวน์โหลด ตัวอักษรไทยใช้ IBM Plex Sans Thai โค้ดใช้ IBM Plex Mono เหมือนเดิม
การค้นหา#
ดัชนี public/docs-search-index.json มีหนึ่งรายการต่อหนึ่งหัวข้อ (ประมาณ 300 รายการ) โหลดครั้งแรกที่เริ่มพิมพ์เท่านั้น ตัวค้นหาใน src/lib/docs-search.ts จับคู่แบบ substring บนข้อความที่ normalize เป็น NFC และตัวพิมพ์เล็ก ภาษาไทยซึ่งไม่มีช่องว่างระหว่างคำจึงค้นได้โดยไม่ต้องตัดคำ คำที่คั่นด้วยช่องว่างต้องพบครบทุกคำ ผลจากชื่อหน้าได้คะแนนสูงกว่าหัวข้อ และหัวข้อสูงกว่าเนื้อหา
ไฟล์ที่เกี่ยวข้อง#
scripts/build-docs.mjs, scripts/render-diagrams.mjs, src/lib/docs.ts, src/lib/docs-search.ts, src/app/docs/layout.tsx, src/app/docs/page.tsx, src/app/docs/[...slug]/page.tsx, src/app/docs/raw/[...slug]/route.ts, src/app/llms.txt/route.ts, src/components/docs/docs-shell.tsx, src/components/docs/doc-view.tsx, src/components/docs/doc-article.tsx, src/components/docs/doc-toc.tsx และ test ใน tests/docs-site.test.ts
Source: docs/architecture/docs-site.md · /docs/architecture/docs-site.md