
จัดการ Content ด้วย pagecms opensource สุดคูล
Introduction
เว็บไซต์นี้พัฒนาโดยใช้ Nuxt ร่วมกับ NuxtContent ซึ่งการจะเป็นการนำเอา Content ในรูปแบบของ Markdown มาสร้างเป็น Web Application โดยเป็นไปตามที่มีการตั้งค่าเอาไว้ ซึ่งวิธีนี้จะทำให้สามารถ Custom ตัวเว็บไซต์ได้เกือบ 100% เลย
ผมเก็บ source code ของ website ไว้ที่ github และ deploy ไปยัง cloudflare page โดยใช้ CICD ของ cloudflare page ครับ ซึ่งวิธีการนี้ทำให้มีค่าใช้จ่ายของเว็บไซต์นี้เกือบจะเป็น 0 บาทต่อเดือนเลยครับ เพราะทุกอย่างใช้จบได้ภายใน Free Tier
แต่ความยากอย่างนึงที่ผมเจอก็คือการที่ต้องจัดการ content ซึ่งอยู่ในรูปแบบของ markdown ช่วงแรก ๆ ก็จะใช้งานผ่าน vscode เอาเลย แต่ช่วงหลังก็เปลี่ยนมาใช้ obsidian โดยปรับแต่งให้สามารถจัดการ content ได้ แต่ก็ยังรู้สึกไม่ค่อยสะดวกเท่าไหร่
จนผมมาเจอเครื่องมือ opensource ตัวนึงชื่อว่า pagecms ซึ่งสามารถแก้ไขปัญหาตรงนี้ให้ผมได้และสะดวกมากขึ้นเยอะเลยครับ
What is pagecms?
pagecms คือ Content Management System สำหรับ Website ที่เป็นประเภท Static Site เช่น Jekyll, Next.js, VuePress, Hugo หรือประเภทใดก็ตามที่มีการเก็บ content ในลักษณะของ File Markdown
ซึ่งตัวเครื่องมือนี้สามารถใช้งานได้แบบง่าย ๆ โดยการเข้าใช้งานที่ https://app.pagescms.org/ จากนั้นทำการเชื่อมต่อกับ Github และเลือก Repo ที่ต้องการได้เลย
สามารถกำหนดการตั้งค่าต่าง ๆ ได้ผ่านตัวเว็บไซต์เลยครับ กำหนด config ให้ตรงกับ project structure โดยดูเพิ่มเติมได้จาก https://pagescms.org/docs/configuration/

สิ่งที่ pagecms สามารถทำได้
- เพิ่ม/ลบ/แก้ไข content ของ website ได้ โดยจะเป็นการ commit เพื่อแก้ไข repo บน github โดยตรง
- สามารถเพิ่ม/ลบ จัดการ media ภายใน website หรือ repo ได้
- สลับ branch ในการจัดการได้
- สามารถ invite คนอื่นเข้ามาร่วมแก้ไขได้ด้วย โดยอันนี้จะเป็นการ invite เข้า repo บน github เลยนะครับ

ซึ่งทั้งหมดจะทำงานบน github อีกทีนึง ตัว pagecms จะเป็นแค่หน้ากากทำให้ใช้งานได้ง่ายยิ่งขึ้นครับ
คำแนะนำ
จากที่ลองใช้งานมารู้สึกโอเคนะครับ แต่ตอนนี้ project กำลังอยู่ในช่วงพัฒนาคิดว่าจะมีฟีเจอร์มาให้ใช้งานอีกเพียบ สามารถติดตามได้ที่ https://github.com/hunvreus/pagescms
แนะนำให้สร้าง branch แยกกับ branch ที่มีการเปิด CICD เอาไว้นะครับ เพราะว่าทุกครั้งที่มีการ Upload File ในช่อง "Media" มันจะเป็นการสร้าง commit ใหม่ ซึ่งจะ trigger CICD เลยครับ แนะนำว่าให้สร้าง branch ใหม่แล้วถ้าต้องการ update ค่อย merge เข้าไปใน branch หลักอีกที
สำหรับผมมองว่าเป็นแค่ Add-on ที่เพิ่มเข้ามา ถ้าวันนึงมันไม่ work แล้วก็สามารถนำออกไปได้เลยครับ
อัปเดต 2026
โพสต์นี้เขียนไว้ตั้งแต่ปี 2025 พอกลับมารื้อ config ใหม่อีกรอบ ก็เจอว่า Pages CMS ขยับไปไกลกว่าที่คิด ของบางอย่างที่เคยเขียนไว้ข้างบนใช้ไม่ได้แล้วครับ
creatable ใน select หายไปแล้ว เดิมผมใส่ creatable: true ไว้ที่ field tags เพื่อจะได้พิมพ์แท็กใหม่จากในหน้าเว็บได้เลย ตอนนี้ไม่มีโค้ดส่วนไหนอ่านคีย์นี้แล้ว select เหลือแค่ค่าที่ประกาศไว้ล่วงหน้าอย่างเดียว อยากเพิ่มแท็กใหม่ก็ต้องมาแก้ที่ values เอง
มี field ชนิด reference มาแทน ชี้ไปที่ collection อื่นหรือ collection ตัวเองก็ได้ แล้วเลือกเอาจาก dropdown ผมย้าย prev / next / recommends จาก string มาเป็นอันนี้หมด ไม่ต้องพิมพ์ slug เองอีก จุดที่ต้องระวังคือค่าที่มันเขียนลงไฟล์เป็นชื่อไฟล์เต็มพร้อม .md ถ้าฝั่งเว็บเอาไปต่อเป็น URL ตรง ๆ ต้องตัดนามสกุลทิ้งก่อน
ตั้งชื่อไฟล์ให้อัตโนมัติได้ ผ่าน filename.template เขียนเป็น pattern อย่าง {year}-{month}-{day}-{ogTitle}.md แต่ token วันที่ตรงนี้คือวันที่ตอนกดสร้าง ไม่ได้ดึงจาก field date ที่กรอกไว้ ถ้าจะย้อนวันที่ก็ต้องแก้ชื่อไฟล์เองอยู่ดี
ล็อกการลบได้ ด้วย operations เปิดปิด create, rename, delete แยกกันได้ ผมปิด delete ไว้ กันมือลั่นลบโพสต์จากหน้าเว็บ
ปุ่มยิง GitHub Actions อันนี้สนุกสุด ประกาศ actions ไว้ใน config แล้ว Pages CMS จะเอาปุ่มไปแปะให้ กดแล้วมัน dispatch workflow พร้อมส่ง payload บอกว่าใครกดและกดมาจากโพสต์ไหน ผมเอามาทำปุ่มรัน typecheck, lint กับ build ก่อน publish
settings คุม commit message ที่ CMS สร้างขึ้นมา กับสั่งให้ merge field ที่ไม่ได้ประกาศไว้แทนที่จะลบทิ้ง อันหลังนี่สำคัญ ถ้าไม่เปิดไว้ field ที่มีอยู่ในไฟล์แต่ไม่มีใน config จะหายไปตอน save
แล้วนี่คือ config ที่ใช้อยู่ตอนนี้ครับ
media:
input: public/images
output: /images
extensions: [jpg, jpeg, png, webp, gif, svg]
settings:
content:
# เก็บ field ที่ไม่ได้ประกาศข้างล่างไว้ ไม่ต้องลบทิ้งตอน save
merge: true
commit:
templates:
create: 'content(blogs): add {path}'
update: 'content(blogs): update {path}'
delete: 'content(blogs): remove {path}'
rename: 'content(blogs): rename {oldPath} to {newPath}'
content:
- name: blogs
label: Blog Posts
type: collection
path: content/blogs
filename:
# slug ของ route คือชื่อไฟล์ ตัดนามสกุล ตัว date prefix เลยสำคัญ
# แต่ {year}/{month}/{day} คือวันที่ตอนกดสร้าง ไม่ได้ตามค่าใน field date
# เป็นแค่ค่าตั้งต้น field: create เปิดให้แก้เองได้
template: '{year}-{month}-{day}-{ogTitle}.md'
field: create
operations:
create: true
rename: true
delete: false
actions:
- name: run-checks
label: Run checks
workflow: pages-cms-checks.yml
scope: collection
ref: current
view:
fields: [title, date, tags, draft]
primary: title
sort: [date, title]
search: [title, description, tags]
default:
sort: date
order: desc
fields:
- name: title
label: Title
type: string
required: true
- name: ogTitle
label: OG Text
type: string
required: true
- name: description
label: Description
type: text
required: true
- name: date
label: Date
type: date
required: true
options:
format: yyyy-MM-dd
- name: tags
label: Tags
type: select
options:
multiple: true
values: [Development, Homelab, AI, Tools, Python, Lifestyle]
- name: draft
label: Draft
type: boolean
default: true
- name: pin
label: Pin
type: boolean
default: false
- name: prev
label: Previous Post
type: reference
options:
collection: blogs
value: '{name}'
label: '{primary}'
search: 'title,description'
- name: next
label: Next Post
type: reference
options:
collection: blogs
value: '{name}'
label: '{primary}'
search: 'title,description'
- name: recommends
label: Recommended Posts
type: reference
options:
collection: blogs
multiple: true
max: 3
value: '{name}'
label: '{primary}'
search: 'title,description'
- name: body
label: Body
type: rich-text
Related Articles


Simple Paginate

