ข้ามไปยังเนื้อหา
Pre-MVP โค้ดเบส 1.0 ยังไม่ใช่ผลิตภัณฑ์ที่เผยแพร่ — ดู ขอบเขตผลิตภัณฑ์ 1.0

การค้นหาสำหรับไซต์ของคุณ

public read API ถูกออกแบบโดยตั้งใจให้ไม่ใช่ query engine (SPEC.md §7) มันให้บริการ snapshot ที่เผยแพร่แล้วตามโมเดล, slug, locale, relation และ tag — ไม่มี ?q= นี่คือการตัดสินใจเชิงออกแบบ ไม่ใช่ช่องโหว่: การสแกนแบบ LIKE บน snapshot ใน D1 จะช้า ไม่มีการจัดอันดับ และเป็นรูปแบบที่ผิดสำหรับโมเดลแบบหนึ่งแถวต่อหนึ่งเอกสาร

การค้นหาเป็นหน้าที่ของฝั่งผู้ใช้ข้อมูล (consumer) มีสามรูปแบบที่ใช้ได้ในปัจจุบัน เรียงตาม ปริมาณงานที่ต้องใช้

หากไซต์ของคุณ build แบบ static (หรือ build ของ API ที่สร้างขึ้นส่งออกเป็น HTML) ให้ทำ index ผลลัพธ์ที่ build แล้ว:

Terminal window
npm install -D pagefind
npx pagefind --site dist

Pagefind ทำ index หน้าที่ render แล้ว ให้บริการ UI แบบ WASM ขนาดเล็กจาก bucket เดียวกัน และไม่ต้องมีเซิร์ฟเวอร์ ให้รันซ้ำใน CI job เดียวกับที่ build ไซต์ นี่คือค่าเริ่มต้นที่เหมาะสมสำหรับไซต์แนวโบรชัวร์

crawl public API เข้าไปในบริการค้นหา แล้ว query บริการนั้นจาก frontend ของคุณ

// One page per model per locale, following the cursor. An item is already
// projected into the requested locale, so index one locale per pass.
const base = 'https://content.example.com/v1';
let cursor: string | undefined;
do {
const url = new URL(`${base}/article`);
url.searchParams.set('locale', 'en');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url);
const { data, page } = await res.json();
for (const item of data) {
await index.upsert({
id: `${item.id}:${item.locale}`,
title: item.fields.title ?? '',
body: item.fields.body ?? '',
slug: item.slug,
locale: item.locale,
published_at: item.published_at,
});
}
// Pagination lives in `page`, beside `data`. The cursor is opaque: send back
// exactly what you were given.
cursor = page.has_more ? page.next_cursor : undefined;
} while (cursor);

ทำ index ใหม่ตามกำหนดเวลา หรือเมื่อได้รับ deploy webhook ที่ CI ของคุณได้รับอยู่แล้ว ใช้ id ของรายการ (รวมกับ locale ของมัน) เป็น key ของ index — slug เปลี่ยนได้ แต่ id ไม่เปลี่ยน

3. Worker สำหรับค้นหาขนาดเล็ก — ไม่ต้องใช้บริการภายนอก

หัวข้อที่มีชื่อว่า “3. Worker สำหรับค้นหาขนาดเล็ก — ไม่ต้องใช้บริการภายนอก”

สำหรับไซต์ขนาดเล็ก Worker หนึ่งตัวที่เก็บ index ไว้ใน memory/KV และรีเฟรชด้วย cron ก็เพียงพอ:

// search-worker: GET /search?q=recipe
// Refresh: cron pulls every public model once per hour.
export default {
async fetch(req, env): Promise<Response> {
const q = new URL(req.url).searchParams.get('q')?.toLowerCase() ?? '';
if (!q) return Response.json({ results: [] });
const index = JSON.parse(await env.SEARCH_INDEX.get('all') ?? '[]');
const results = index
.filter((d) => (d.title + ' ' + d.body).toLowerCase().includes(q))
.slice(0, 20);
return Response.json({ results });
},
async scheduled(_event, env, _ctx) {
const res = await fetch(`${env.PUBLIC_API}/article?locale=en&limit=100`);
const { data } = await res.json();
await env.SEARCH_INDEX.put('all', JSON.stringify(
data.map((d) => ({ title: d.fields.title, body: strip(d.fields.body), slug: d.slug }))
));
},
};

นี่คือการจับคู่ substring ไม่ใช่การจัดอันดับ — ใช้ได้ดีเมื่อมีเอกสารไม่เกินสองสามพันรายการ แต่ไม่เหมาะเมื่อเกินกว่านั้น ให้ย้ายไปใช้ Typesense เมื่อเริ่มเป็นปัญหา

  • ทำ index เฉพาะ snapshot ที่เผยแพร่แล้วเท่านั้น ผ่าน public API admin API ไม่ส่งข้อมูลเข้า search index เด็ดขาด draft และเนื้อหาที่ยังไม่เผยแพร่ต้องไม่รั่วไหลออกไป
  • ใช้ id ของรายการเป็น key ของเอกสาร และแสดงผลด้วย slug
  • ทำ index ใหม่หลัง deploy ไม่ใช่หลังการเผยแพร่ทุกครั้ง — public API มี cache tag ดังนั้น index ที่ล้าสมัยไปไม่กี่นาทีถือเป็นเรื่องปกติและไม่เป็นอันตราย
  • เคารพ locale: locale เป็นพารามิเตอร์ของ request และรายการจะถูกส่งกลับมา โดยถูก project เป็น locale เดียวแล้ว ดังนั้นให้ crawl หนึ่งครั้งต่อหนึ่ง locale และเก็บเอกสาร index หนึ่งชิ้นต่อหนึ่ง locale หรือใช้ locale facet — อย่ารวมเป็นก้อนเดียวเด็ดขาด

สิ่งที่จะทำให้การค้นหากลายเป็นส่วนหนึ่งของผลิตภัณฑ์

หัวข้อที่มีชื่อว่า “สิ่งที่จะทำให้การค้นหากลายเป็นส่วนหนึ่งของผลิตภัณฑ์”

build ของ graduation ดึงข้อมูลไซต์อยู่แล้วเพื่อสร้าง API ที่มี type การส่งออก search index แบบ static เป็น build artifact อีกชิ้นหนึ่งสอดคล้องกับแนวคิดหลัก: index จะกลายเป็นไฟล์ที่ลูกค้าเป็นเจ้าของ และรีเฟรชโดย CI ตัวเดียวกับที่ build API ของพวกเขาใหม่ นั่นคือจุดที่ “การค้นหา” จะกลายเป็นฟีเจอร์ของ Dee Wan แทนที่จะเป็นสูตรสำหรับฝั่ง consumer