ข้ามไปเนื้อหาหลัก

● กำลังพัฒนา

Reading Log

Static site สำหรับบันทึกหนังสือที่อ่าน พร้อม rating, note และ tags จัดการด้วย Astro Content Collections ไม่ต้องมี backend

· อ่านประมาณ 1 นาที

สารบัญ

แรงบันดาลใจ

Goodreads มีฟีเจอร์เยอะเกินจำเป็น และ track ข้อมูลผู้ใช้มากเกินไป สิ่งที่ต้องการจริงๆ คือพื้นที่ส่วนตัวสำหรับบันทึกว่า “อ่านอะไร คิดอะไร ได้อะไรไปบ้าง” โดยไม่มีโฆษณาหรือ social pressure

โครงสร้างข้อมูล

แต่ละหนังสือเก็บเป็น markdown file พร้อม frontmatter — Zod validate ทุก field ตั้งแต่ build time:

---
title: "Show Your Work"
author: "Austin Kleon"
status: completed
rating: 5
startDate: 2026-03-01
endDate: 2026-03-15
tags: [creativity, career, non-fiction]
cover: /covers/show-your-work.jpg
---

## Quote ที่ชอบ

> "You don't have to be a genius. Just be yourself."

## สิ่งที่ได้จากหนังสือเล่มนี้

ทำให้กล้า share งานที่ยังไม่สมบูรณ์แบบออกมา

Zod Schema

const books = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/books' }),
  schema: z.object({
    title: z.string(),
    author: z.string(),
    status: z.enum(['reading', 'completed', 'want-to-read']).default('want-to-read'),
    rating: z.number().min(1).max(5).optional(),
    startDate: z.coerce.date().optional(),
    endDate: z.coerce.date().optional(),
    tags: z.array(z.string()).default([]),
    cover: z.string().optional(),
  }),
});

ฟีเจอร์

  • Shelf view — แสดงหนังสือตามสถานะ: กำลังอ่าน / อ่านแล้ว / อยากอ่าน
  • Reading stats — จำนวนหนังสือต่อปี, genres ที่อ่านมากที่สุด, เฉลี่ย rating
  • Tag filter — กรองหนังสือตาม topic หรือ genre
  • Note per book — บันทึก quote และ key insight ที่ต้องการจำ

Reading Stats ที่คำนวณ build time

const completed = books.filter(b => b.data.status === 'completed');
const booksThisYear = completed.filter(b => {
  const year = b.data.endDate?.getFullYear();
  return year === new Date().getFullYear();
});
const avgRating = completed
  .filter(b => b.data.rating)
  .reduce((sum, b) => sum + (b.data.rating ?? 0), 0) / completed.length;

Stats ทั้งหมดคำนวณตอน build ไม่ต้องมี API call — ผลลัพธ์ embed ลงใน HTML ไปเลย

สิ่งที่เรียนรู้

  • Optional fields ใน Zod — ใช้ .optional() และ .coerce.date() สำหรับ dates ที่มาจาก frontmatter
  • Derived data — สร้าง stats จาก collections โดยตรง ไม่ต้องมีฐานข้อมูล
  • z.coerce.date() — แปลง string จาก YAML เป็น Date object อัตโนมัติ ต่างจาก z.date() ที่ต้องการ Date object แล้วเท่านั้น
  • Progressive enhancement — เพิ่ม JS filter ทีหลัง โดย HTML ยังทำงานได้โดยไม่มี JS