- Change output: 'static' to output: 'server' with node adapter - Add @astrojs/node adapter documentation - Update Static vs SSR explanation for Easypanel deployment
1169 lines
34 KiB
Markdown
1169 lines
34 KiB
Markdown
---
|
||
name: website-creator
|
||
description: สร้างเว็บไซต์เต็มรูปแบบด้วย Astro + Tina CMS พร้อม Workflow สำหรับเว็บใหม่และ Migration ครอบคลุม Design System, Content Collections, Auth, SEO, PDPA Compliance และ Deploy
|
||
tags: [astro, website, website-development, website-creation, migration, tailwindcss, thai, pdpa, seo, tina-cms, astro-db, image-generation, picture-it]
|
||
category: software-development
|
||
related_skills:
|
||
- spec-driven-development
|
||
- frontend-ui-engineering
|
||
- api-and-interface-design
|
||
- code-review-and-quality
|
||
- performance-optimization
|
||
- browser-testing-with-devtools
|
||
- shipping-and-launch
|
||
---
|
||
|
||
# Website Creator Skill
|
||
|
||
สร้างเว็บไซต์เต็มรูปแบบด้วย Astro + Tina CMS
|
||
|
||
## Architecture
|
||
|
||
**Astro + Tina CMS Stack:**
|
||
- **Astro 6.x** — Static site generator ที่เร็วมาก รองรับ React/Vue/Svelte components
|
||
- **Tina CMS** — Self-hosted Git-based CMS สำหรับ visual content editing
|
||
- **Astro DB** — Built-in database (libSQL) สำหรับ consent logs และ dynamic content
|
||
- **Tailwind CSS 4.x** — ใช้ `@tailwindcss/vite` plugin
|
||
|
||
**Pattern อ้างอิง:** ใช้ template ที่มีอยู่แล้ว: `templates/astro-tina-starter/`
|
||
|
||
### Astro Project Structure
|
||
|
||
```
|
||
src/
|
||
├── components/ # Astro/React components
|
||
│ └── consent/ # PDPA consent system
|
||
├── content/ # Tina CMS content (MDX)
|
||
│ ├── posts/ # Blog posts
|
||
│ ├── pages/ # Static pages
|
||
│ └── settings/ # Site settings (JSON)
|
||
├── layouts/
|
||
│ └── Layout.astro # Main layout
|
||
├── pages/
|
||
│ ├── index.astro # Home page
|
||
│ └── [...slug].astro # Dynamic pages
|
||
├── stores/ # Nano Stores (client state)
|
||
├── styles/
|
||
│ └── global.css # Tailwind v4 + @theme
|
||
├── db/
|
||
│ └── config.ts # Astro DB schema
|
||
└── env.d.ts
|
||
|
||
.tina/ # Tina CMS configuration
|
||
├── config.ts # Tina config
|
||
└── schema.ts # Content schema
|
||
|
||
astro.config.mjs # Astro + Tailwind v4 + Tina
|
||
package.json
|
||
```
|
||
|
||
### Static vs SSR
|
||
|
||
- **SSR (default สำหรับ consent):** ใช้ `output: 'server'` สำหรับ API routes + consent logging
|
||
- **Static:** Pre-built HTML + รันบน CDN (ไม่มี backend)
|
||
- **Hybrid:** บางหน้า static บางหน้า dynamic
|
||
|
||
**สำหรับ Easypanel:** ใช้ SSR mode เพื่อรองรับ Astro DB API routes
|
||
|
||
## Critical Configuration Rules
|
||
|
||
### 1. astro.config.mjs (Tailwind v4 + Tina + SSR)
|
||
|
||
```javascript
|
||
import { defineConfig } from 'astro/config'
|
||
import tailwindcss from '@tailwindcss/vite'
|
||
import tina from 'tinacms'
|
||
import node from '@astrojs/node'
|
||
|
||
export default defineConfig({
|
||
integrations: [
|
||
tina({
|
||
enabled: !!process.env.TINA_TOKEN,
|
||
sidebar: { partials: [] },
|
||
}),
|
||
],
|
||
vite: {
|
||
plugins: [tailwindcss()], // Tailwind v4 ใช้ @tailwindcss/vite
|
||
},
|
||
output: 'server',
|
||
adapter: node({ mode: 'standalone' }),
|
||
server: {
|
||
port: 4321,
|
||
},
|
||
})
|
||
```
|
||
|
||
### 2. Required Dependencies
|
||
|
||
```json
|
||
{
|
||
"dependencies": {
|
||
"astro": "^6.1.7",
|
||
"@tinacms/cli": "^2.1.0",
|
||
"tinacms": "^2.2.0",
|
||
"@astrojs/db": "^0.14.0",
|
||
"nanostores": "^0.11.0",
|
||
"@nanostores/preact": "^0.5.0"
|
||
},
|
||
"devDependencies": {
|
||
"@tailwindcss/vite": "^4.0.0",
|
||
"tailwindcss": "^4.0.0",
|
||
"@astrojs/mdx": "^4.0.0",
|
||
"typescript": "^5.6.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
**CRITICAL: Tailwind v4 ใช้ `@tailwindcss/vite` plugin ไม่ใช่ `@astrojs/tailwind`**
|
||
|
||
### 3. Tailwind v4 Configuration
|
||
|
||
Tailwind v4 ไม่มี `tailwind.config.js` — ใช้ CSS `@theme` block แทน:
|
||
|
||
```css
|
||
/* src/styles/global.css */
|
||
@import "tailwindcss";
|
||
|
||
@theme {
|
||
/* Fonts */
|
||
--font-sans: "Inter", "Noto Sans Thai", system-ui, sans-serif;
|
||
|
||
/* Colors */
|
||
--color-primary-50: #f8fafc;
|
||
--color-primary-900: #0f172a;
|
||
--color-accent-500: #3b82f6;
|
||
|
||
/* Border Radius */
|
||
--radius-sm: 0.25rem;
|
||
--radius-lg: 0.75rem;
|
||
--radius-xl: 1rem;
|
||
}
|
||
```
|
||
|
||
### 4. Tina CMS Schema
|
||
|
||
```typescript
|
||
// .tina/schema.ts
|
||
import { defineSchema } from 'tinacms'
|
||
|
||
export const schema = defineSchema({
|
||
collections: [
|
||
{
|
||
name: 'post',
|
||
label: 'Posts',
|
||
path: 'src/content/posts',
|
||
format: 'mdx',
|
||
fields: [
|
||
{ type: 'string', name: 'title', label: 'Title', required: true },
|
||
{ type: 'string', name: 'slug', label: 'Slug', required: true },
|
||
{ type: 'datetime', name: 'publishedAt', label: 'Published At' },
|
||
{ type: 'rich-text', name: 'body', label: 'Body', isBody: true },
|
||
],
|
||
},
|
||
{
|
||
name: 'page',
|
||
label: 'Pages',
|
||
path: 'src/content/pages',
|
||
format: 'mdx',
|
||
fields: [
|
||
{ type: 'string', name: 'title', label: 'Title', required: true },
|
||
{ type: 'string', name: 'slug', label: 'Slug', required: true },
|
||
{ type: 'rich-text', name: 'body', label: 'Body', isBody: true },
|
||
],
|
||
},
|
||
],
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## Workflow A: สร้างเว็บใหม่
|
||
|
||
### Step 1: Pre-Project Questions
|
||
|
||
ใช้ `references/questions.md` เป็นแนวทาง:
|
||
|
||
```
|
||
1. ชื่อเว็บไซต์/บริษัท?
|
||
2. ทำอะไร? (ขายอะไร/ให้บริการอะไร)
|
||
3. กลุ่มเป้าหมายคือใคร?
|
||
4. มีเว็บอยู่แล้วหรือยัง?
|
||
5. ชอบสี/ดีไซน์แบบไหน?
|
||
6. มี logo file ไหม?
|
||
7. ต้องการหน้าอะไรบ้าง?
|
||
8. มี SMTP/email สำหรับส่งเมลไหม?
|
||
9. มี GA4/Marketing tools ไหม?
|
||
10. มี DPO หรือยัง?
|
||
```
|
||
|
||
---
|
||
|
||
### Step 2: Plan Sitemap + Content Structure
|
||
|
||
**เรียก skill: `spec-driven-development`** — เขียน SPEC.md ก่อนลงมือทุกครั้ง
|
||
|
||
แสดง sitemap ให้ user ดูก่อนเสมอ รอ approve ก่อนลงมือ:
|
||
|
||
```
|
||
/ # Home
|
||
/about # About Us
|
||
/services # Services overview
|
||
/contact # Contact + Form
|
||
/blog # Blog listing (ถ้ามี)
|
||
/blog/[slug] # Individual blog post
|
||
/privacy-policy # PDPA Privacy Policy
|
||
/terms-of-service # PDPA Terms of Service
|
||
```
|
||
|
||
---
|
||
|
||
### Step 3: Design Framework
|
||
|
||
**เรียก skills: `ckm:design` + `frontend-ui-engineering` + `ckm:ui-styling` + `ui-ux-pro-max`**
|
||
|
||
ขั้นตอน:
|
||
1. ถามว่ามี brand guidelines หรือ reference ไหม
|
||
2. ถ้าไม่มี → ถาม 2 คำถาม:
|
||
- ชอบ dark mode ไหม หรือ light mode อย่างเดียว?
|
||
- ชอบ style แบบไหน: minimal, bold, creative?
|
||
3. ใช้ `ckm:design` เพื่อสร้าง design system
|
||
4. ใช้ `ui-ux-pro-max` เพื่อออกแบบ wireframes
|
||
5. ใช้ `ckm:ui-styling` เพื่อสร้าง components
|
||
|
||
---
|
||
|
||
### Step 4: Setup Astro + Tina CMS Project
|
||
|
||
**ใช้ script ที่มีอยู่แล้ว:**
|
||
|
||
```bash
|
||
# สร้าง project ใหม่จาก template
|
||
bash skills/website-creator/scripts/new-project.sh my-website
|
||
|
||
# หรือระบุ path
|
||
bash skills/website-creator/scripts/new-project.sh my-website /path/to/projects/
|
||
```
|
||
|
||
**Script ทำอะไร:**
|
||
1. Copy template `templates/astro-tina-starter/`
|
||
2. เพิ่ม PDPA consent system
|
||
3. Copy legal templates (privacy policy, terms)
|
||
4. ติดตั้ง dependencies
|
||
5. สร้าง .env file
|
||
6. Initialize git
|
||
|
||
**เปิด browser:**
|
||
- Frontend: http://localhost:4321
|
||
- Tina Admin: http://localhost:4321/admin (dev mode)
|
||
|
||
---
|
||
|
||
### Step 5: พัฒนา Components + Pages
|
||
|
||
**เรียก skills: `spec-driven-development` + `api-and-interface-design` + `ckm:design` + `ckm:ui-styling` + `frontend-ui-engineering`**
|
||
|
||
สร้างตาม sitemap ที่วางแผนไว้:
|
||
- FrontendLayout, Navigation, Footer
|
||
- Pages (Home, About, Services, Contact)
|
||
- Blog listing + detail pages
|
||
- Forms (Contact form ส่ง email จริง)
|
||
|
||
**สำคัญ — แยก Design Layer กับ Content Layer:**
|
||
|
||
Design skill (ui-ux-pro-max) ออกแบบ **หน้าตา + layout + animation** — ไม่ใช่ content structure
|
||
Tina CMS เก็บ **เนื้อหา (ข้อความ, format, links, images)** — ไม่ใช่ layout
|
||
|
||
ทั้งสองอยู่คนละ layer กัน → ต้องแยกทำ แล้วมารวมกันตอน integrate
|
||
|
||
---
|
||
|
||
### Step 5b: สร้างภาพประกอบด้วย `picture-it`
|
||
|
||
**เรียก skill: `picture-it`**
|
||
|
||
สำหรับทุก page/section ที่ต้องการ hero image, illustration หรือภาพประกอบ:
|
||
|
||
**วิธีใช้:**
|
||
|
||
```bash
|
||
# Load credentials ก่อน
|
||
set -a && source ~/.config/opencode/.env && set +a
|
||
export PATH="/home/kunthawat/snap/bun-js/87/.bun/bin:$PATH"
|
||
|
||
# ตรวจสอบ Thai font patch
|
||
bun ~/.hermes/skills/website-creator/creative/picture-it/scripts/thai-font-patch.ts --force
|
||
```
|
||
|
||
**Workflow สร้างภาพประกอบเว็บ:**
|
||
|
||
| Use Case | Pipeline | ค่าใช้จ่าย |
|
||
|----------|----------|-----------|
|
||
| Hero background | `generate flux-schnell` + `text` (Thai) + `grade cinematic` + `vignette` | ~$0.003 |
|
||
| Service illustration | `generate flux-schnell` + `remove-bg` (ถ้ามี product) + `compose` | ~$0.01 |
|
||
| Blog hero | `generate flux-schnell` + `edit seedream` (place logo) + `grade` | ~$0.043 |
|
||
| Social/OG image | `generate flux-schnell` + `text` + `grade` | ~$0.003 |
|
||
| Team/About photo | `generate flux-schnell` → realistic headshot style | ~$0.003 |
|
||
|
||
**ขนาดภาพแนะนำ:**
|
||
|
||
| Purpose | Size |
|
||
|---------|------|
|
||
| Hero banner | 1200×630 หรือ 1920×1080 |
|
||
| Blog hero | 1200×630 |
|
||
| OG Image (social share) | 1200×630 |
|
||
| Service card | 800×600 |
|
||
| Team member | 400×400 (circle crop) |
|
||
| Logo (ในภาพ) | 200×200 หรือเล็กกว่า |
|
||
|
||
**Workflow ตัวอย่าง — Hero Section:**
|
||
|
||
```bash
|
||
# 1. Generate background
|
||
picture-it generate \
|
||
--prompt "modern tech workspace, clean minimal, yellow #fed400 accent lighting" \
|
||
--size 1200x630 \
|
||
--model flux-schnell \
|
||
-o hero-bg.png
|
||
|
||
# 2. เพิ่ม Thai text
|
||
picture-it text \
|
||
-i hero-bg.png \
|
||
--title "บริการรับทำเว็บไซต์ SEO" \
|
||
--font "Kanit" \
|
||
--font-size 48 \
|
||
-o hero-texted.png
|
||
|
||
# 3. Color grade
|
||
picture-it grade -i hero-texted.png --name cinematic -o hero-final.png
|
||
```
|
||
|
||
**การบันทึกภาพไว้ใน project:**
|
||
|
||
```
|
||
src/
|
||
├── components/
|
||
│ └── Hero.astro # Hero component
|
||
└── assets/
|
||
├── heroes/ # Hero images
|
||
├── services/ # Service illustrations
|
||
├── blog/ # Blog heroes
|
||
└── og/ # Open Graph images
|
||
```
|
||
|
||
---
|
||
|
||
### Step 5c: Design + Tina Content Integration
|
||
|
||
**Design layer กับ Content layer แยกกัน — ค่อยรวมตอน build**
|
||
|
||
#### สิ่งที่ต้องเข้าใจ
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ ui-ux-pro-max / ckm:design — Design Layer │
|
||
│ • Component structure (Hero, Card, Navbar) │
|
||
│ • Color tokens, typography, spacing │
|
||
│ • Animation specs (150-300ms, ease-out) │
|
||
│ • Layout grid, responsive breakpoints │
|
||
│ • Interaction states (hover, press, disabled) │
|
||
│ Output: Astro + Tailwind code (component skeleton) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ Tina CMS — Content Layer │
|
||
│ • ข้อความ + format (bold, italic, link) │
|
||
│ • Headings (H1-H6) │
|
||
│ • Lists, blockquotes, code blocks │
|
||
│ • Images, links │
|
||
│ Output: MDX files (เนื้อหา) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ Integration — ครอบ Tina content ด้วย Design │
|
||
│ • Design component ครอบ MDX output │
|
||
│ • Animation class ที่ wrapper element │
|
||
│ • Design tokens apply ผ่าน Tailwind prose │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### ขั้นตอนที่ถูกต้อง
|
||
|
||
```
|
||
[1] Design Phase
|
||
ui-ux-pro-max → Component structure, tokens, animations
|
||
Output: Component skeleton (ไม่มี content)
|
||
↓
|
||
[2] Tina Phase
|
||
สร้าง Content Collections + MDX files
|
||
Output: Content structure ใน Tina (src/content/)
|
||
↓
|
||
[3] Content Phase
|
||
พิมพ์ content ใน Tina Admin (/admin)
|
||
Output: MDX files
|
||
↓
|
||
[4] Integration Phase
|
||
ครอบ Tina content ด้วย Design components
|
||
```
|
||
|
||
#### ตัวอย่าง: Page Structure (Design Output)
|
||
|
||
Design skill อาจให้ component แบบนี้:
|
||
|
||
```astro
|
||
---
|
||
// ❌ สิ่งที่ design skill อาจให้มา — hardcode content
|
||
---
|
||
|
||
<div class="hero-section">
|
||
<h1>Welcome to Our Site</h1> <!-- hardcode -->
|
||
<p class="hero-desc">Amazing content here...</p> <!-- hardcode -->
|
||
</div>
|
||
```
|
||
|
||
ต้องแปลงเป็น:
|
||
|
||
```astro
|
||
---
|
||
// ✅ ครอบ Tina content ด้วย design component
|
||
import { Container, Heading, Prose } from '@/components/ui';
|
||
|
||
interface Props {
|
||
title: string;
|
||
body: string; // จาก MDX frontmatter หรือ Tina query
|
||
}
|
||
|
||
const { title, body } = Astro.props;
|
||
---
|
||
|
||
<Container class="py-20">
|
||
<Heading level={1} class="text-5xl font-bold mb-6">{title}</Heading>
|
||
{body && (
|
||
<Prose>
|
||
<slot /> <!-- MDX content จะมาอยู่ตรงนี้ -->
|
||
</Prose>
|
||
)}
|
||
</Container>
|
||
```
|
||
|
||
#### Tina Content Collections
|
||
|
||
กำหนดว่า content แต่ส่วนเก็บใน collection ไหน:
|
||
|
||
```typescript
|
||
// .tina/schema.ts
|
||
const schema = defineSchema({
|
||
collections: [
|
||
{
|
||
name: 'post',
|
||
label: 'Posts',
|
||
path: 'src/content/posts',
|
||
fields: [
|
||
{ type: 'string', name: 'title', label: 'Title', required: true },
|
||
{ type: 'string', name: 'slug', label: 'Slug', required: true },
|
||
{ type: 'string', name: 'description', label: 'Description' },
|
||
{ type: 'datetime', name: 'publishedAt', label: 'Published At' },
|
||
{ type: 'rich-text', name: 'body', label: 'Body', isBody: true },
|
||
],
|
||
},
|
||
{
|
||
name: 'page',
|
||
label: 'Pages',
|
||
path: 'src/content/pages',
|
||
fields: [
|
||
{ type: 'string', name: 'title', label: 'Title', required: true },
|
||
{ type: 'string', name: 'slug', label: 'Slug', required: true },
|
||
{ type: 'rich-text', name: 'body', label: 'Body', isBody: true },
|
||
],
|
||
},
|
||
{
|
||
name: 'settings',
|
||
label: 'Settings',
|
||
path: 'src/content/settings',
|
||
format: 'json',
|
||
fields: [
|
||
{ type: 'string', name: 'siteName', label: 'Site Name' },
|
||
{ type: 'string', name: 'siteDescription', label: 'Description' },
|
||
],
|
||
},
|
||
],
|
||
})
|
||
```
|
||
|
||
#### วิธี Integrate: Design Components + Tina Content
|
||
|
||
```astro
|
||
---
|
||
// src/pages/blog/[slug].astro
|
||
import Layout from '@/layouts/Layout.astro';
|
||
import { Container, Heading, Prose } from '@/components/ui';
|
||
|
||
// Astro ดึง content จาก MDX files
|
||
const { slug } = Astro.params;
|
||
|
||
// ด้วย Astro content collections
|
||
const { post } = Astro.props;
|
||
---
|
||
|
||
<Layout title={post.data.title} description={post.data.description}>
|
||
<Container class="py-20">
|
||
<Heading level={1} class="text-5xl font-bold mb-6">
|
||
{post.data.title}
|
||
</Heading>
|
||
|
||
{post.data.description && (
|
||
<p class="text-xl text-gray-600 mb-8">{post.data.description}</p>
|
||
)}
|
||
|
||
<Prose>
|
||
<!-- MDX content จะ render ตรงนี้ -->
|
||
<slot />
|
||
</Prose>
|
||
</Container>
|
||
</Layout>
|
||
```
|
||
|
||
---
|
||
|
||
### Step 7: PDPA Compliance
|
||
|
||
**ใช้ templates ที่มีอยู่แล้ว:**
|
||
|
||
1. **Privacy Policy** — ใช้ `templates/privacy-policy.md`
|
||
- ภาษากฎหมาย PDPA ครบถ้วน
|
||
- แทนที่ placeholders ด้วยข้อมูลจริง
|
||
|
||
2. **Terms of Service** — ใช้ `templates/terms-of-service.md`
|
||
|
||
3. **PDPA Topics ที่ต้องครอบคลุม:**
|
||
|
||
**3.1 Cookie Consent Popup**
|
||
- ไม่ block content
|
||
- Options: Accept All, Reject All, Customize
|
||
- ถ้า reject → ไม่ load GA4/marketing scripts
|
||
|
||
**3.2 Consent Logging (PDPA)**
|
||
- เก็บ log ลง Astro DB (ไม่ต้อง MongoDB ภายนอก)
|
||
- ระบุ: action, purpose, analytics/marketing/functional flags, ip, userAgent, timestamp
|
||
|
||
**ไฟล์ที่ต้องสร้าง:**
|
||
|
||
1. **`ConsentBanner.astro`** — Consent banner component (มีใน template)
|
||
|
||
```astro
|
||
---
|
||
// src/components/consent/ConsentBanner.astro
|
||
import { consentStore } from '@/stores/consent';
|
||
---
|
||
|
||
<div id="pdpa-consent-banner" class="consent-banner">
|
||
<h3>🍪 การยินยอมตาม พ.ร.บ.คุ้มครองข้อมูลส่วนบุคคล</h3>
|
||
<p>เราใช้คุกกี้เพื่อปรับปรุงประสบการณ์...</p>
|
||
|
||
<div class="consent-banner__buttons">
|
||
<button id="consent-accept-all">ยอมรับทั้งหมด</button>
|
||
<button id="consent-reject-all">ปฏิเสธทั้งหมด</button>
|
||
<button id="consent-show-preferences">ตั้งค่าคุกกี้</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
import { consentStore } from './stores/consent';
|
||
// ... consent logic
|
||
</script>
|
||
```
|
||
|
||
2. **`db/config.ts`** — Astro DB schema:
|
||
|
||
```typescript
|
||
// src/db/config.ts
|
||
import { defineDb, column } from 'astro:db';
|
||
|
||
export const ConsentLog = defineTable({
|
||
columns: {
|
||
id: column serial({ primaryKey: true }),
|
||
action: column text(),
|
||
purpose: column text(),
|
||
analytics: column boolean({ default: false }),
|
||
marketing: column boolean({ default: false }),
|
||
functional: column boolean({ default: false }),
|
||
userAgent: column text({ optional: true }),
|
||
ip: column text({ optional: true }),
|
||
timestamp: column date({ default: { now: true } }),
|
||
sessionId: column text({ optional: true }),
|
||
},
|
||
});
|
||
```
|
||
|
||
3. **`src/pages/api/consent.ts`** — API endpoint:
|
||
|
||
```typescript
|
||
// src/pages/api/consent.ts
|
||
import type { APIRoute } from 'astro';
|
||
import { db, ConsentLog } from 'astro:db';
|
||
|
||
export const POST: APIRoute = async ({ request }) => {
|
||
const body = await request.json();
|
||
const { action, purpose, analytics, marketing, functional } = body;
|
||
|
||
const ip = request.headers.get('x-forwarded-for')?.split(',')[0] || 'unknown';
|
||
const userAgent = request.headers.get('user-agent') || 'unknown';
|
||
|
||
await db.insert(ConsentLog).values({
|
||
action,
|
||
purpose,
|
||
analytics,
|
||
marketing,
|
||
functional,
|
||
userAgent,
|
||
ip,
|
||
});
|
||
|
||
return new Response(JSON.stringify({ success: true }), {
|
||
status: 200,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
});
|
||
};
|
||
```
|
||
|
||
4. **เพิ่มใน astro.config.mjs:**
|
||
|
||
```javascript
|
||
// astro.config.mjs
|
||
import { defineConfig } from 'astro/config';
|
||
import tailwindcss from '@tailwindcss/vite';
|
||
import tina from 'tinacms';
|
||
|
||
export default defineConfig({
|
||
integrations: [tina()],
|
||
vite: { plugins: [tailwindcss()] },
|
||
output: 'server',
|
||
adapter: node({ mode: 'standalone' }),
|
||
});
|
||
```
|
||
|
||
**3.3 Right to be Forgotten**
|
||
- API endpoint สำหรับลบข้อมูล user
|
||
|
||
---
|
||
|
||
### Step 8: SEO Setup
|
||
|
||
**เรียก skills: `seo-analyzers` + `seo-geo` + `seo-multi-channel`**
|
||
|
||
1. **Meta tags ทุกหน้า** — ใช้ frontmatter จาก MDX หรือ static
|
||
2. **sitemap.xml** — Astro ใช้ `@astrojs/sitemap`
|
||
3. **robots.txt**
|
||
4. **Open Graph images** — **ใช้ `picture-it` สร้าง OG image template ที่ consistent**
|
||
5. **JSON-LD structured data**
|
||
6. **Thai language optimization**
|
||
|
||
```bash
|
||
# ติดตั้ง sitemap
|
||
npx astro add sitemap
|
||
```
|
||
|
||
```javascript
|
||
// astro.config.mjs
|
||
import sitemap from '@astrojs/sitemap';
|
||
|
||
export default defineConfig({
|
||
site: 'https://example.com',
|
||
integrations: [sitemap(), tina()],
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### Step 9: Preview + QA
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
**ก่อน QA — เรียก skills ตามลำดับ:**
|
||
|
||
1. **`code-review-and-quality`** — Full multi-axis code review ก่อน delivery
|
||
2. **`performance-optimization`** — Performance audit + fixes
|
||
3. **`browser-testing-with-devtools`** — ทดสอบใน real browser
|
||
|
||
**จากนั้นเรียก `dogfood` สำหรับ exploratory QA:**
|
||
|
||
```bash
|
||
/skill dogfood
|
||
"ทดสอบ user journey:
|
||
1. ดู home page → คลิก services
|
||
2. ดู about us
|
||
3. กรอก contact form
|
||
4. ทดสอบ cookie consent popup
|
||
5. ตรวจ PDPA compliance (privacy policy, terms)
|
||
6. ตรวจ mobile responsive
|
||
7. ตรวจ SEO meta tags ทุกหน้า"
|
||
```
|
||
|
||
ให้ user ตรวจสอบ → รอ feedback → แก้ไข → รอ approve
|
||
|
||
---
|
||
|
||
### Step 10: Deploy
|
||
|
||
**เรียก skill: `easypanel-deploy`**
|
||
|
||
```bash
|
||
/skill easypanel-deploy
|
||
"deploy ไปยัง easypanel server
|
||
server: openclaw-vps
|
||
project: my-website
|
||
domain: example.com
|
||
git repo: https://github.com/user/my-website"
|
||
```
|
||
|
||
---
|
||
|
||
## Workflow B: ปรับปรุงเว็บที่มีอยู่แล้ว (Existing Repo)
|
||
|
||
### ความหมายของ "Existing Repo"
|
||
|
||
Repo ที่มีโครงสร้าง Astro + Tina แล้ว แต่ยังไม่สมบูรณ์ — เช่น:
|
||
- มี starter template แต่ไม่มี content
|
||
- มี design tokens แล้วแต่ยังไม่มี pages
|
||
- มีบางหน้าแต่ต้องการปรับปรุงเนื้อหา/design
|
||
|
||
**ไม่ใช่** 新规 creation — มี codebase อยู่แล้ว ปรับในที่เดิม
|
||
|
||
---
|
||
|
||
### ขั้นตอน
|
||
|
||
```
|
||
[1] ตรวจสอบ repo ที่มี (terminal/find ดูโครงสร้าง)
|
||
│
|
||
[2] สำรวจ: astro.config.mjs, components, pages, design tokens
|
||
│
|
||
[3] สำรวจ content ที่มี (Posts, Pages จากเว็บเดิม) → วางแผน Migration
|
||
│
|
||
[4] Migrate content เข้า Tina CMS
|
||
│
|
||
[5] ตรวจสอบ /admin ว่าเห็น Posts + Pages พร้อมแก้ไขได้
|
||
│
|
||
[6] Integrate: อัปเดต Pages ให้อ่านจาก Tina แทน hardcode
|
||
│ (ดู **Design + Tina Integration** ด้านบน)
|
||
│
|
||
[7] สรุปสิ่งที่มี vs สิ่งที่ขาด → แผน Sitemap
|
||
│
|
||
[8] ถามคำถามที่ขาด (content, portfolio, pricing, dark/light)
|
||
│
|
||
[9] รอ approve
|
||
↓
|
||
จากนั้น → Workflow A Step 3 เป็นต้นไป (พัฒนา Components + Pages)
|
||
```
|
||
|
||
---
|
||
|
||
## Migration: Next.js + Payload → Astro + Tina
|
||
|
||
**ใช้ script ที่มีอยู่แล้ว:**
|
||
|
||
```bash
|
||
# Migrate existing website ไป Astro + Tina
|
||
bash skills/website-creator/scripts/migrate-tina.sh /path/to/existing-site /path/to/migrated-site
|
||
```
|
||
|
||
**Script ทำอะไร:**
|
||
1. Detect source technology (Astro, Next.js, Remix, etc.)
|
||
2. Analyze source content
|
||
3. Copy Astro+Tina template
|
||
4. Migrate content (MD/MDX files)
|
||
5. Add PDPA consent system
|
||
6. Create Tina schema
|
||
7. Generate migration report
|
||
|
||
### Content Migration Checklist
|
||
|
||
| จาก Payload | ไป Tina |
|
||
|------------|---------|
|
||
| `payload.find()` | Astro content collections |
|
||
| `RichText` component | MDX files |
|
||
| `payload.config.ts` collections | `.tina/schema.ts` collections |
|
||
| MongoDB/PostgreSQL | Astro DB (built-in) |
|
||
| `/admin` | `/admin` (Tina visual editor) |
|
||
| Payload Auth | Tina Auth (optional) |
|
||
|
||
---
|
||
|
||
## Astro Content Collections
|
||
|
||
Astro ใช้ content collections สำหรับ type-safe content management:
|
||
|
||
### สร้าง Collection
|
||
|
||
```typescript
|
||
// src/content/config.ts
|
||
import { defineCollection, z } from 'astro:content';
|
||
|
||
const postsCollection = defineCollection({
|
||
type: 'content',
|
||
schema: z.object({
|
||
title: z.string(),
|
||
description: z.string().optional(),
|
||
publishedAt: z.date().optional(),
|
||
author: z.string().optional(),
|
||
image: z.string().optional(),
|
||
}),
|
||
});
|
||
|
||
const pagesCollection = defineCollection({
|
||
type: 'content',
|
||
schema: z.object({
|
||
title: z.string(),
|
||
description: z.string().optional(),
|
||
}),
|
||
});
|
||
|
||
export const collections = {
|
||
posts: postsCollection,
|
||
pages: pagesCollection,
|
||
};
|
||
```
|
||
|
||
### ใช้ Collection
|
||
|
||
```astro
|
||
---
|
||
// src/pages/blog/[slug].astro
|
||
import { getCollection } from 'astro:content';
|
||
|
||
export async function getStaticPaths() {
|
||
const posts = await getCollection('posts');
|
||
return posts.map((post) => ({
|
||
params: { slug: post.slug },
|
||
props: { post },
|
||
}));
|
||
}
|
||
|
||
const { post } = Astro.props;
|
||
const { Content } = await post.render();
|
||
---
|
||
|
||
<article>
|
||
<h1>{post.data.title}</h1>
|
||
<Content />
|
||
</article>
|
||
```
|
||
|
||
---
|
||
|
||
## Tina CMS Integration
|
||
|
||
### Local Development
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
Tina จะ available ที่ `http://localhost:4321/admin`
|
||
|
||
### Production Setup
|
||
|
||
```bash
|
||
# รัน script สำหรับติดตั้ง Tina backend
|
||
bash scripts/install-tina-backend.sh
|
||
```
|
||
|
||
**ต้องมี:**
|
||
- `TINA_TOKEN` — Production authentication token
|
||
- `TINA_CLIENT_ID` — Tina client ID
|
||
|
||
### Tina Schema Best Practices
|
||
|
||
```typescript
|
||
// .tina/schema.ts
|
||
const schema = defineSchema({
|
||
collections: [
|
||
{
|
||
name: 'post',
|
||
label: 'Blog Posts',
|
||
path: 'src/content/posts',
|
||
fields: [
|
||
{
|
||
type: 'string',
|
||
name: 'title',
|
||
label: 'Title',
|
||
isTitle: true, // ใช้เป็น title ใน admin UI
|
||
required: true,
|
||
},
|
||
{
|
||
type: 'string',
|
||
name: 'slug',
|
||
label: 'URL Slug',
|
||
required: true,
|
||
},
|
||
{
|
||
type: 'datetime',
|
||
name: 'publishedAt',
|
||
label: 'Publish Date',
|
||
},
|
||
{
|
||
type: 'rich-text',
|
||
name: 'body',
|
||
label: 'Content',
|
||
isBody: true, // เป็น body หลัก
|
||
},
|
||
],
|
||
},
|
||
],
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## Templates
|
||
|
||
### Astro Tina Starter (`templates/astro-tina-starter/`)
|
||
|
||
Base template ที่รวมทุกอย่างพร้อม:
|
||
|
||
```
|
||
astro-tina-starter/
|
||
├── .tina/
|
||
│ ├── config.ts # Tina CMS configuration
|
||
│ └── schema.ts # Content schema definitions
|
||
├── db/
|
||
│ ├── config.ts # Astro DB schema
|
||
│ └── seed.ts # Database seed script
|
||
├── src/
|
||
│ ├── styles/
|
||
│ │ └── global.css # Tailwind v4 styles + @theme
|
||
│ ├── layouts/
|
||
│ │ └── Layout.astro
|
||
│ ├── pages/
|
||
│ │ └── index.astro
|
||
│ ├── components/
|
||
│ │ └── Header.astro
|
||
│ └── content/
|
||
│ ├── config.ts # Astro content collections
|
||
│ ├── posts/ # Blog posts (MDX)
|
||
│ ├── pages/ # Static pages (MDX)
|
||
│ └── settings/ # Site settings (JSON)
|
||
├── public/
|
||
│ └── favicon.svg
|
||
├── Dockerfile
|
||
├── astro.config.mjs
|
||
├── tsconfig.json
|
||
└── package.json
|
||
```
|
||
|
||
**Features ที่มี:**
|
||
- Astro 6.1.7 + Tina CMS 2.x
|
||
- Tailwind CSS 4.x with `@tailwindcss/vite`
|
||
- Astro DB for consent logging
|
||
- Nano Stores for client state
|
||
- Thai language support
|
||
- Docker-ready
|
||
|
||
### PDPA Consent Template (`templates/consent/`)
|
||
|
||
Template สำหรับ PDPA consent system:
|
||
|
||
```
|
||
consent/
|
||
├── ConsentBanner.astro # Consent banner component
|
||
├── api/
|
||
│ ├── consent.ts # API endpoints
|
||
│ └── route.ts # Astro API route
|
||
├── db/
|
||
│ └── config.ts # Astro DB ConsentLog table
|
||
└── stores/
|
||
└── consent.ts # Nano Stores
|
||
```
|
||
|
||
---
|
||
|
||
## Docker
|
||
|
||
### Development
|
||
|
||
```bash
|
||
docker compose up -d
|
||
```
|
||
|
||
### Production (Multi-stage Dockerfile)
|
||
|
||
```dockerfile
|
||
FROM node:22-alpine AS deps
|
||
RUN corepack enable && corepack prepare pnpm@9.0.0 --activate
|
||
WORKDIR /app
|
||
COPY package.json pnpm-lock.yaml* ./
|
||
RUN pnpm install --frozen-lockfile
|
||
|
||
FROM deps AS builder
|
||
COPY . .
|
||
RUN pnpm build
|
||
|
||
FROM node:22-alpine AS runner
|
||
WORKDIR /app
|
||
ENV NODE_ENV production
|
||
RUN adduser --system --uid 1001 astro
|
||
USER astro
|
||
EXPOSE 8080
|
||
CMD ["node", "dist/server/entry.mjs"]
|
||
```
|
||
|
||
**Build command:**
|
||
```bash
|
||
npm run build
|
||
```
|
||
|
||
---
|
||
|
||
## Sub-skill Routing
|
||
|
||
| ขั้นตอน | Sub-skill | รายละเอียด |
|
||
|---------|-----------|-------------|
|
||
| **ตลอด workflow** | `spec-driven-development` | เขียน spec ก่อน implement ทุกครั้ง |
|
||
| Design Framework | `ckm:design` | Brand, tokens, design system |
|
||
| Design Framework | `frontend-ui-engineering` | UI component architecture, patterns |
|
||
| UI Components | `ckm:ui-styling` | shadcn/ui, Tailwind CSS |
|
||
| UI/UX Design | `ui-ux-pro-max` | Wireframes, mockups, 50+ design patterns |
|
||
| Brand Identity | `brand` | Logo, colors, typography, voice |
|
||
| Banner/Hero Images | `banner-design` | Social, ads, web heroes |
|
||
| Develop Components + Pages | `api-and-interface-design` | API design, component interfaces |
|
||
| Before QA | `code-review-and-quality` | Full multi-axis code review |
|
||
| Before QA | `performance-optimization` | Performance audit + fixes |
|
||
| QA Testing | `browser-testing-with-devtools` | Real browser testing |
|
||
| QA Testing | `dogfood` | Exploratory QA of web app |
|
||
| SEO Planning | `seo-context` | Per-project SEO context |
|
||
| SEO Audit | `seo-analyzers` | Thai language SEO analysis |
|
||
| GEO (AI Search) | `seo-geo` | AI Overviews, llms.txt, crawler |
|
||
| Multi-channel Content | `seo-multi-channel` | Facebook, LinkedIn, blog content |
|
||
| Deploy | `easypanel-deploy` | Easypanel hosting |
|
||
| Design + Tina Integration | `frontend-ui-engineering` | แยก design layer กับ content layer |
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
### Build fails: "Cannot find module '@tailwindcss/vite'"
|
||
|
||
**Error:** `Cannot find module '@tailwindcss/vite'`
|
||
|
||
**ทางแก้:**
|
||
|
||
```bash
|
||
# ติดตั้ง @tailwindcss/vite
|
||
npm install -D @tailwindcss/vite
|
||
|
||
# หรือถ้าใช้ Tailwind v3 syntax อยู่ → ต้องเปลี่ยนเป็น v4
|
||
# ใน global.css:
|
||
@import "tailwindcss"; # ไม่ใช่ @tailwind base; @tailwind components; @tailwind utilities;
|
||
```
|
||
|
||
### Astro DB not working
|
||
|
||
**Error:** `Cannot find module 'astro:db'` หรือ tables not found
|
||
|
||
**ทางแก้:**
|
||
|
||
```bash
|
||
# Push schema to database
|
||
npm run db:push
|
||
|
||
# ตรวจสอบว่ามี astro:db import ถูกต้อง
|
||
import { db, ConsentLog } from 'astro:db';
|
||
```
|
||
|
||
### Tina Admin not loading
|
||
|
||
**อาการ:** `/admin` แสดง blank page หรือ error
|
||
|
||
**ทางแก้:**
|
||
|
||
1. ตรวจสอบว่า Tina config ถูกต้อง:
|
||
```javascript
|
||
// astro.config.mjs
|
||
import tina from 'tinacms';
|
||
|
||
export default defineConfig({
|
||
integrations: [tina()],
|
||
});
|
||
```
|
||
|
||
2. ตรวจสอบว่า `.tina/schema.ts` มีอยู่
|
||
|
||
3. ลองรัน `npm run dev` ใหม่
|
||
|
||
### Port already in use
|
||
|
||
**Error:** `Port 4321 is already in use`
|
||
|
||
**ทางแก้:**
|
||
|
||
```bash
|
||
# หา process ที่ใช้ port
|
||
lsof -i :4321
|
||
|
||
# kill process
|
||
kill <PID>
|
||
|
||
# หรือรัน port อื่น
|
||
npm run dev -- --port 4322
|
||
```
|
||
|
||
### MDX content not rendering
|
||
|
||
**อาการ:** MDX files ไม่แสดง content
|
||
|
||
**ทางแก้:**
|
||
|
||
```astro
|
||
---
|
||
// ต้องใช้ .render() เพื่อดึง Content component
|
||
const { Content } = await post.render();
|
||
---
|
||
|
||
<Content />
|
||
```
|
||
|
||
### Static build vs SSR
|
||
|
||
**อาการ:** API routes ไม่ทำงานหลัง build
|
||
|
||
**ทางแก้:**
|
||
|
||
ถ้าต้องการใช้ API routes (เช่น consent API):
|
||
|
||
```javascript
|
||
// astro.config.mjs
|
||
export default defineConfig({
|
||
output: 'hybrid', // หรือ 'server' สำหรับทุกหน้า dynamic
|
||
});
|
||
```
|
||
|
||
แล้วเพิ่ม `export const prerender = false;` ใน API route files.
|
||
|
||
---
|
||
|
||
## Key Principles
|
||
|
||
1. **Plan-First Approach**
|
||
- แสดงแผนก่อนลงมือเสมอ
|
||
- รอ user approve ก่อน
|
||
|
||
2. **Astro Latest**
|
||
- ใช้ Astro 6.x เวอร์ชั่นล่าสุด
|
||
- Tina CMS สำหรับ content management
|
||
|
||
3. **Thai-First**
|
||
- ภาษาไทยเป็นหลัก
|
||
- Thai fonts (Kanit, Noto Sans Thai)
|
||
- Thai typography CSS
|
||
- Thai structured data (LocalBusiness, Organization)
|
||
|
||
4. **Preview Before Deploy**
|
||
- Preview ผ่าน local dev server ที่ `0.0.0.0`
|
||
- ให้ user ตรวจสอบก่อน deploy
|
||
|
||
5. **Tailwind v4**
|
||
- ใช้ `@tailwindcss/vite` plugin
|
||
- ใช้ `@theme` block แทน tailwind.config.js
|
||
- ไม่ใช้ `@astrojs/tailwind` (deprecated)
|