การสร้างเว็บไซต์ไดเรกทอรีดูเหมือนจะเป็นเรื่องง่าย จนกว่าคุณจะต้องมานั่งวางระบบฐานข้อมูล, เลเยอร์การทำแคช และเฟรมเวิร์กหน้าบ้านแบบ reactive เพียงเพื่อจะแสดงผลสิ่งที่แท้จริงแล้วก็คือสารบัญที่คัดสรรมาอย่างดี ล่าสุดผมได้สร้าง Social Tools List ซึ่งเป็นเว็บไซต์สำหรับเปรียบเทียบซอฟต์แวร์โซเชียลมีเดีย เป้าหมายของผมคือการทำให้มันออนไลน์ได้อย่างรวดเร็ว รักษาความเร็ว และหลีกเลี่ยงการต้องดูแลโครงสร้างพื้นฐานสำหรับเนื้อหาที่เปลี่ยนแปลงเฉพาะตอนที่ผมเพิ่มหรืออัปเดตเครื่องมือเท่านั้น ผมจึงตัดสินใจเลือกใช้สแต็กแบบ static-first: ใช้ Astro สำหรับการสร้างไซต์, TypeScript สำหรับข้อมูลที่มีโครงสร้าง และ Cloudflare Workers สำหรับการ deploy ผลลัพธ์ที่ได้คือเว็บไซต์ที่โหลดได้ทันที มีค่าโฮสติ้งที่แทบจะเป็นศูนย์ และไม่ต้องมีการดูแลจัดการฐานข้อมูลเลย
ทำไมแนวคิด Static-First ถึงเหมาะสมสำหรับเว็บไซต์ไดเรกทอรี
แอปเว็บจำนวนมากมักจะเลือกใช้การเรนเดอร์ฝั่งเซิร์ฟเวอร์หรือสถาปัตยกรรมแบบ single-page เป็นค่าเริ่มต้น เพราะรู้สึกว่าเป็นทางเลือกที่ปลอดภัยและทันสมัย แต่ไม่ใช่ทุกเว็บไซต์ที่จะต้องรับข้อมูลจากผู้ใช้แบบไดนามิกในทุกๆ การเรียกใช้งาน Social Tools List เป็นทรัพยากรที่เน้นการอ่านเป็นหลัก (read-heavy) ข้อมูลการเปรียบเทียบจะเปลี่ยนก็ต่อเมื่อผมส่งอัปเดตเข้าไป ไม่ใช่เมื่อผู้เข้าชมกดรีเฟรชหน้าเว็บ การเรนเดอร์ HTML ไว้ล่วงหน้าช่วยลดความจำเป็นในการคิวรีฐานข้อมูลที่ edge, การคอมไพล์เทมเพลตแบบเรียลไทม์ หรือภาระจากการทำ hydration ในเบราว์เซอร์ ผมสร้างไซต์ในขั้นตอน build, deploy ไฟล์สแตติก และปล่อยให้ worker น้ำหนักเบาทำหน้าที่เป็นตัวครอบ (wrapper) วิธีนี้ช่วยให้เวลาในการตอบสนองต่ำและกำจัดปัญหาความล้มเหลวในขณะรันไทม์ (runtime failures) ไปได้ทั้งกลุ่ม
เก็บข้อมูลใน TypeScript ไม่ใช่ในฐานข้อมูล
ผมไม่ได้ใช้ฐานข้อมูล เครื่องมือทุกชิ้นในไดเรกทอรีถูกกำหนดให้เป็น TypeScript object ที่มี slug, name, domain และ array ของ workflow ที่รองรับ ตัวอย่างข้อมูลทั่วไปจะมีลักษณะดังนี้:
{
slug: 'buffer',
name: 'Buffer',
domain: 'buffer.com',
workflows: ['scheduling', 'analytics']
}
การเก็บข้อมูลในภาษาเดียวกับที่ใช้สร้างเว็บไซต์มีประโยชน์ทันทีสองประการ ประการแรก pull requests จะกลายเป็นการรีวิวเนื้อหา เมื่อผมเพิ่มเครื่องมือใหม่ ตัว diff จะแสดงฟิลด์และค่าที่ถูกต้องอย่างชัดเจน และเพื่อนร่วมทีมสามารถตรวจพบคำผิดหรือโดเมนที่ผิดได้โดยไม่ต้องเรียนรู้อินเทอร์เฟซของ CMS ประการที่สอง คอมไพเลอร์ของ TypeScript จะช่วยควบคุมโครงสร้างของทุกเรคคอร์ด หากผมลืมใส่ slug หรือพิมพ์คีย์ของ workflow ผิด การ build จะล้มเหลวทันทีก่อนที่ข้อมูลที่ผิดพลาดจะไปถึงหน้าเว็บ
การใช้ฐานข้อมูลจะนำมาซึ่งการทำ migrations, connection strings, กลยุทธ์การทำแคช และขั้นตอนการสำรองข้อมูล สำหรับไดเรกทอรีที่มีข้อมูลเพียงไม่กี่ร้อยรายการที่ผมดูแลด้วยตัวเอง ภาระส่วนเกินเหล่านั้นถือเป็นตัวถ่วงอย่างยิ่ง ข้อมูลแบบสแตติกใน TypeScript modules จึงเป็นทางเลือกที่ถูกต้องและประหยัดที่สุดสำหรับโมเดลนี้ คำว่า "ถูกต้อง" นั้นสำคัญ เพราะมันไม่ใช่แค่เรื่องของการประหยัดเงิน แต่คือการกำจัดเลเยอร์การทำ abstraction ที่เข้ามาแก้ปัญหาที่ผมไม่ได้มีอยู่จริง
ให้ Astro จัดการเรื่อง Routing และ Rendering
Astro สร้างหน้า HTML หนึ่งหน้าต่อหนึ่งเครื่องมือจากไดนามิกรูทเพียงชุดเดียว ผมกำหนด layout component เพียงอันเดียว และ Astro จะจัดการสร้าง metadata, headings และข้อมูลแบบโครงสร้างสำหรับทุกรายการให้โดยอัตโนมัติ เนื่องจากชุดข้อมูลเดียวกันถูกนำไปใช้ทั้งในหน้าดัชนีหลัก, ศูนย์รวมหมวดหมู่ workflow และหน้ารายละเอียดแยกย่อย จึงไม่มีโอกาสที่การ์ดบนหน้าแรกจะแสดงคำอธิบายที่แตกต่างจากหน้ารายละเอียดเอง ในการตั้งค่า CMS แบบดั้งเดิม คุณมักจะเห็นความคลาดเคลื่อน (drift): API ส่งข้อมูลเวอร์ชันหนึ่ง, แคชส่งอีกเวอร์ชัน และการเรนเดอร์ฝั่งไคลเอนต์ส่งอีกเวอร์ชันหนึ่ง การสร้างแบบสแตติกจากแหล่งข้อมูลความจริงหนึ่งเดียว (single source of truth) ช่วยป้องกันปัญหานั้นได้
สถาปัตยกรรมแบบ island ของ Astro ยังช่วยให้เพิ่มส่วนที่โต้ตอบได้ขนาดเล็กเข้าไปได้ง่าย โดยไม่ทำให้ทั้งหน้าเว็บต้องหนักไปด้วย JavaScript เว็บไซต์จะถูกส่งออกไปเป็น HTML สแตติก และมีเพียงสคริปต์การกรองข้อมูลเท่านั้นที่ทำหน้าที่ hydrate ในส่วนเฉพาะของ DOM โดยไม่มี framework runtime มาครอบเอกสารทั้งฉบับ นอกจากนี้ Astro ยังให้ความสำคัญกับ page metadata เป็นอันดับต้นๆ หน้าเครื่องมือแต่ละหน้าจะมี title tag และ meta description ของตัวเองซึ่งดึงมาจาก TypeScript record โดยตรง ดังนั้นผมจึงไม่จำเป็นต้องใช้ปลั๊กอินแยกหรือไลบรารีจัดการ head เลย
การกรองข้อมูลโดยไม่ต้องใช้เฟรมเวิร์ก
การค้นหาและการกรองข้อมูลมักจะกระตุ้นให้เหล่านักพัฒนาติดตั้ง React, Vue หรือไลบรารีจัดการสถานะ (state management) ที่มีขนาดใหญ่ แต่ผมเลือกที่จะไม่ทำเช่นนั้น Astro เรนเดอร์รายการทั้งหมดเป็น HTML ธรรมดาบนเซิร์ฟเวอร์ จากนั้นใช้สคริปต์ vanilla JavaScript ขนาดเล็กที่มีขนาดไม่ถึงหนึ่งกิโลไบต์ รันในเบราว์เซอร์เพื่อสลับคุณสมบัติ display ของรายการต่างๆ ตามแท็ก workflow หรือข้อความที่ตรงกัน
Serving the complete markup sounds inefficient if you come from an API-driven background. But consider the overhead of a typical dynamic approach. The browser downloads a JavaScript bundle, hydrates a component tree, calls an endpoint, waits for JSON, and then renders rows. For a directory that lists fewer than one hundred tools, that ritual is slower and less reliable than hiding divs that are already in the document. My script attaches event listeners to filter buttons, reads a data-workflow attribute on each row, and sets non-matches to hidden. The operation takes milliseconds.
Because the list is present in the initial HTML, the site is usable without JavaScript. Search engine crawlers see every link and every description. Users on slow networks or with script blockers still get the full directory. The filtering is an enhancement, not a gate.
Sitemaps and Robots as Code
Sitemaps and robots.txt are not afterthoughts written by hand. They are Astro routes that consume the same dataset and URL helpers as
