เว็บนี้: พอร์ตโฟลิโอสองภาษาที่มี CMS ของตัวเอง
เว็บโปรไฟล์ไทย/อังกฤษพร้อมแอดมิน Ant Design: ฉบับร่างและประวัติเวอร์ชัน, ล็อกอินด้วย passkey, ไลบรารีสื่อบน Cloudflare R2, backup นอกระบบแบบเข้ารหัส, ตัวแก้ธีม และบล็อกพร้อม RSS — สร้างด้วย Next.js 16, Prisma 7, PostgreSQL บน Railway หลัง Cloudflare
- Next.js
- TypeScript
- React
- Prisma
- PostgreSQL
- Ant Design
- Cloudflare R2
- Railway
- GitHub Actions
ภาพรวม
เว็บส่วนตัวควรดูแลถูกและอัปเดตง่าย เว็บนี้จึงเป็นแอป Next.js ตัวเดียวที่มีสองหน้า: หน้า public แบบมี motion ทั้งไทยและอังกฤษ กับแอดมิน Ant Design ที่ใช้แก้ไข ร่าง และเผยแพร่เนื้อหาทุกชิ้น ไม่มี headless CMS ไม่มีผู้ให้บริการล็อกอินภายนอก ทั้งระบบคือหนึ่งโปรเซส หนึ่งฐานข้อมูล PostgreSQL และสคริปต์อีกไม่กี่ตัว
ข้างในมีอะไร
- เนื้อหามีวงจรชีวิต โปรไฟล์ ประสบการณ์ โปรเจกต์ และบทความ เดินผ่าน draft → published → archived การแก้ของที่เผยแพร่แล้วจะกลายเป็น "การเปลี่ยนแปลงที่ยังไม่เผยแพร่" จนกว่าจะกด Publish changes และทุกครั้งที่เผยแพร่จะเก็บเป็นเวอร์ชันที่ย้อนกลับได้
- สองภาษาในเรคอร์ดเดียว ภาษาอังกฤษอยู่ในแถวหลัก ภาษาไทยอยู่ในแถวคำแปล และ fallback ทีละช่อง โปรเจกต์ที่แปลไม่ครบก็ยังแสดงผลได้
- Passkey มาก่อน เจ้าของล็อกอินด้วย passkey (WebAuthn) รหัสผ่านเป็นแค่ทางสำรองตอนเริ่มต้น เปลี่ยนรหัสผ่านแล้วอุปกรณ์อื่นจะถูกตัดออกทั้งหมด
- ไลบรารีสื่อ ไม่ใช่ช่องกรอก URL ไฟล์อัปโหลดตรงไป Cloudflare R2 ผ่าน presigned URL อ้างอิงด้วย checksum (อัปโหลดไฟล์เดิมซ้ำจะได้ object เดิม) และรู้ว่าตัวเองถูกใช้ที่ไหน ลบรูปที่หน้าไหนยังใช้อยู่ไม่ได้
- ตัวแก้ธีม สี 4 สีต่อโหมดถูกขยายเป็น design token ราว 85 ตัวใน OKLCH พร้อมตรวจ contrast และพรีวิวสดจากหน้า public จริง
- บทความ เขียนด้วย markdown มีแท็ก การ์ดแชร์ที่ generate ให้ต่อบทความ และ RSS แยกตามภาษา
รันอย่างไร
Railway โฮสต์ทั้งแอปและฐานข้อมูลอยู่หลัง Cloudflare GitHub Actions รัน typecheck, lint, test ราว 550 ตัว และ build จริงทุกครั้งที่ push, apply migration ที่ค้างอยู่ ก่อน Railway จะ build และ dump ฐานข้อมูลทุกคืนไปเก็บใน bucket ส่วนตัวแบบเข้ารหัสด้วยกุญแจที่ CI ไม่มี ข้อความจากฟอร์มติดต่อถูกบันทึกก่อนแล้วค่อยส่งอีเมล ระบบเมลล่มก็ไม่มีข้อความหาย
Diagram sourceflowchart
flowchart TD people["ผู้เข้าชมและเจ้าของเว็บ"] --> cf["Cloudflare"] cf --> app["แอป Next.js บน Railway"] app --> db[("PostgreSQL")] app --> r2[("Cloudflare R2 เก็บไฟล์")] app -.->|หลังบันทึกข้อความแล้ว| mail["อีเมล"] push["git push"] --> ci["GitHub Actions ตรวจและ build"] ci -->|migration ก่อน| db ci -->|แล้ว Railway จึง build| app nightly["workflow ทุกคืน"] -.->|dump ที่ปิดผนึกแล้ว| bucket[("bucket ส่วนตัว")]
สิ่งที่ได้เรียนรู้
บทเรียนแพง ๆ อยู่ที่ฝั่ง operations ไม่ใช่หน้าเว็บ: build แบบ static ที่อ่านฐานข้อมูลจะพังทันทีที่ migration เพิ่มคอลัมน์ที่ build ต้องใช้ migration จึงต้องรันใน CI ก่อน และ backup ที่ไม่เคยถูก restore เป็นแค่สมมติฐาน การ restore จึงเป็นสคริปต์ที่ซ้อมได้ ไม่ใช่ความหวัง
- สถานะ
- เผยแพร่แล้ว
- เผยแพร่เมื่อ
- 1 มี.ค. 2024
- เทคโนโลยี
- Next.js · TypeScript · React · Prisma · PostgreSQL · Ant Design · Cloudflare R2 · Railway · GitHub Actions
- ลิงก์
- เว็บไซต์จริง
ดูต่อ

