Cách dùng Claude API + bash script tự build static blog từ Markdown

Chia sẻ bài viết

Mục lục
TL;DR
  • Build static blog từ Markdown bằng bash + curl + Claude API, không cần Hugo/Jekyll.
  • Claude generate meta description, summary, related posts tự động.
  • Output HTML deploy thẳng lên nginx VPS, không cần Node.js runtime.
  • Cost: 1 USD Claude API đủ build 500 bài post.
  • Phù hợp dev solo muốn full control, không bị lock framework.

Bạn viết blog Markdown, muốn deploy lên VPS riêng nhưng ngại Hugo (Go), Jekyll (Ruby), Astro (Node). Mỗi cái có quirk riêng, dependency hell. Bash + Claude API là combo minimalist: 200 dòng script làm SSG nhỏ, AI tự generate metadata, không cần framework.

Cách này phù hợp dev solo muốn personal blog plain HTML, hoặc startup muốn landing page tĩnh nhanh. Output là HTML thuần, deploy lên nginx VPS 2GB serve được hàng triệu request/ngày không vấn đề.

Bài này hướng dẫn viết full script: parse Markdown, gọi Claude generate meta/excerpt, render HTML từ template, build sitemap, deploy nginx. Test trên Cloud VPS TND 2GB Debian 12.

1. Cấu trúc thư mục project

my-blog/
├── posts/
│   ├── 2026-05-23-hello-world.md
│   └── 2026-05-30-tips-vps.md
├── templates/
│   ├── post.html
│   ├── index.html
│   └── style.css
├── build.sh
├── deploy.sh
└── dist/    (output)
    ├── index.html
    ├── posts/
    │   ├── hello-world/index.html
    │   └── tips-vps/index.html
    ├── sitemap.xml
    └── style.css

2. Cài dependency

# Markdown parser
sudo apt install -y pandoc jq curl

# Claude API key
export ANTHROPIC_API_KEY=sk-ant-xxx

# Verify
pandoc --version
jq --version

3. Viết script build.sh - parse Markdown

#!/bin/bash
# build.sh
set -e

SRC="posts"
DIST="dist"
TEMPLATE="templates"

rm -rf $DIST
mkdir -p $DIST/posts

# Copy static assets
cp $TEMPLATE/style.css $DIST/

# Loop posts
for md in $SRC/*.md; do
  filename=$(basename "$md" .md)
  # Parse frontmatter: 2026-05-23-hello-world.md -> date + slug
  date=$(echo $filename | cut -d'-' -f1-3)
  slug=$(echo $filename | cut -d'-' -f4-)

  echo "Processing $slug..."

  # Convert MD to HTML body
  body=$(pandoc "$md" -f markdown -t html)

  # Extract title (first H1)
  title=$(head -1 "$md" | sed 's/^# //')

  # Call Claude to generate meta description
  excerpt=$(./scripts/claude-meta.sh "$md")

  # Render từ template
  mkdir -p "$DIST/posts/$slug"
  sed -e "s/{{TITLE}}/$title/g" 
      -e "s/{{DATE}}/$date/g" 
      -e "s/{{EXCERPT}}/$excerpt/g" 
      $TEMPLATE/post.html | 
      sed "s|{{BODY}}|$body|g" 
      > "$DIST/posts/$slug/index.html"
done

4. Script claude-meta.sh - gọi Claude API

#!/bin/bash
# scripts/claude-meta.sh
# Input: file markdown
# Output: meta description 155 char

MD_FILE=$1
CONTENT=$(cat "$MD_FILE")

PROMPT="Đọc bài blog markdown sau và viết 1 meta description SEO tiếng Việt,
chính xác 150-155 ký tự, mời gọi click, không tag, trả về plain text:

---
$CONTENT
---"

curl -s https://api.anthropic.com/v1/messages 
  -H "x-api-key: $ANTHROPIC_API_KEY" 
  -H "anthropic-version: 2023-06-01" 
  -H "content-type: application/json" 
  -d "$(jq -n --arg prompt "$PROMPT" '{
    model: "claude-3-5-haiku-20241022",
    max_tokens: 200,
    messages: [{role: "user", content: $prompt}]
  }')" 
  | jq -r '.content[0].text' 
  | tr -d 'n' 
  | head -c 155

Dùng Claude Haiku rẻ nhất (~0.25 USD/1M token). Mỗi bài 500 từ -> ~700 token -> ~0.0002 USD. 500 bài chỉ tốn 0.1 USD.

5. Template post.html





  
  
  {{TITLE}} - My Blog
  
  
  
  


  

{{TITLE}}

{{BODY}}
← Back to home

6. Build index.html với danh sách post

# Thêm vào build.sh sau loop
echo "Building index..."

cat > $DIST/index.html <<'HTML'


My Blog


Blog của tôi

    HTML # Sort by date desc for md in $(ls -r $SRC/*.md); do filename=$(basename "$md" .md) date=$(echo $filename | cut -d'-' -f1-3) slug=$(echo $filename | cut -d'-' -f4-) title=$(head -1 "$md" | sed 's/^# //') echo "
  • $title -
  • " >> $DIST/index.html done cat >> $DIST/index.html <<'HTML'
HTML

7. Tạo sitemap.xml tự động

# Thêm vào build.sh
echo "Generating sitemap..."

cat > $DIST/sitemap.xml <<'XML'


XML

for md in $SRC/*.md; do
  filename=$(basename "$md" .md)
  date=$(echo $filename | cut -d'-' -f1-3)
  slug=$(echo $filename | cut -d'-' -f4-)
  echo "  https://blog.com/posts/$slug/$date" >> $DIST/sitemap.xml
done

echo "" >> $DIST/sitemap.xml

8. Claude generate related posts (advanced)

# scripts/claude-related.sh
# Tìm 3 post liên quan dựa trên semantic similarity

MD_FILE=$1
ALL_TITLES=$(ls posts/*.md | xargs -I{} basename {} .md | cut -d'-' -f4- | tr 'n' ',')

PROMPT="Bài blog hiện tại: $(head -50 $MD_FILE)
Danh sách tất cả slug: $ALL_TITLES
Chọn 3 slug phù hợp nhất làm related posts, trả về JSON array: ["slug1", "slug2", "slug3"]"

curl -s https://api.anthropic.com/v1/messages 
  -H "x-api-key: $ANTHROPIC_API_KEY" 
  -H "anthropic-version: 2023-06-01" 
  -H "content-type: application/json" 
  -d "$(jq -n --arg p "$PROMPT" '{
    model: "claude-3-5-haiku-20241022",
    max_tokens: 200,
    messages: [{role: "user", content: $p}]
  }')" 
  | jq -r '.content[0].text'

9. Deploy script lên nginx VPS

# deploy.sh
#!/bin/bash
set -e

VPS="user@vps-ip"
REMOTE_DIR="/var/www/blog"

./build.sh

echo "Deploying to $VPS:$REMOTE_DIR..."
rsync -avz --delete dist/ $VPS:$REMOTE_DIR/

ssh $VPS "sudo systemctl reload nginx"
echo "Done!"

10. Nginx config cho static blog

# /etc/nginx/sites-available/blog
server {
    listen 80;
    server_name blog.com www.blog.com;
    root /var/www/blog;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    # Cache static asset 1 năm
    location ~* .(css|js|jpg|png|webp|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Gzip
    gzip on;
    gzip_types text/css text/html application/json;
}

sudo ln -s /etc/nginx/sites-available/blog /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# SSL với certbot
sudo certbot --nginx -d blog.com -d www.blog.com

11. Auto rebuild khi commit git

# Trên VPS, tạo webhook receive Git push
sudo apt install -y webhook

# /etc/webhook.conf
[
  {
    "id": "rebuild-blog",
    "execute-command": "/var/www/build-trigger.sh",
    "command-working-directory": "/var/www/blog-src"
  }
]

# /var/www/build-trigger.sh
#!/bin/bash
cd /var/www/blog-src
git pull origin main
./build.sh
rsync -avz --delete dist/ /var/www/blog/
sudo systemctl reload nginx

# Start webhook
sudo systemctl enable --now webhook

# GitHub repo Settings -> Webhooks
# URL: https://blog.com:9000/hooks/rebuild-blog
# Trigger: push event

12. So sánh với SSG framework

Tiêu chíBash + ClaudeHugoAstro
Setup time30 phút1 giờ2 giờ
DependencyBash, pandoc, jqGo binaryNode 100MB
Build speed (100 post)2 phút1 giây30 giây
CustomizationToàn quyền sửa scriptTheme + shortcodeComponent MDX
SEO autoClaude generateManualManual
Learning curveBash basicHCL templateJSX
Cloud VPS cho vibe coder

Static blog VPS 2GB serve triệu request/ngày

Cloud VPS TND sẵn AlmaLinux 9, Ubuntu 22/24, Debian 12/13. SSD CEPH, snapshot 1-click, backup hằng ngày, network 200Mbps trong nước. Nginx serve HTML tĩnh không lo down, chi phí 199k/tháng.

Xem 8 cấu hình Cloud VPS →

FAQ

Bash script có ổn định cho blog 1000+ bài không?

Có. Bash xử lý 1000 file/30 giây không vấn đề. Bottleneck là Claude API call. Nên cache excerpt vào .json file, chỉ gọi lại khi MD thay đổi.

Cost Claude API thực tế khi build 100 bài là bao nhiêu?

Dùng Haiku model: ~0.02 USD cho 100 bài (mỗi bài ~500 từ). Sonnet đắt hơn 10 lần. Haiku đủ tốt cho generate meta description ngắn.

Có hỗ trợ syntax highlight code block không?

Có. Pandoc render code block với class language-xxx, dùng Prism.js hoặc Highlight.js client-side. Hoặc dùng pandoc --highlight-style=github để render server-side.

RSS feed có generate tự động không?

Thêm 1 vòng loop trong build.sh: cho mỗi post export ... vào file rss.xml. Pandoc có template RSS, gọi pandoc --template rss.tpl.

Có thể dùng Gemini hoặc GPT thay Claude không?

Có. Đổi endpoint và format request trong claude-meta.sh. Gemini Flash rẻ hơn Claude Haiku 5x cho task ngắn. Cost ~0.005 USD cho 100 bài.

Bash script khó maintain không?

Phù hợp dưới 300 dòng. Vượt thì nên switch sang Python hoặc Go cho dễ debug. Bash mạnh cho prototype, không lý tưởng cho long-term complex logic.

2009
15+ năm vận hành liên tục
10+
tập đoàn lớn tin dùng
100+
doanh nghiệp SMB Việt
30 ngày
đổi key lỗi miễn phí
Phần mềm bản quyền chính hãng chúng tôi cung cấp
Bản quyền chính hãng Hóa đơn VAT đầy đủ Đổi key lỗi 30 ngày Vận hành từ 2009 MST 0200994870 Hotline 0225.999.6666