Mục lục

Project: Auto Blog Writer

Workflow tự động tạo bài viết chuẩn SEO bằng AI và commit trực tiếp vào GitHub Repository.

Đây là ví dụ thực tế về một quy trình tự động hóa mạnh mẽ: Từ ý tưởng (Topic) -> AI Viết bài (JSON) -> Tạo file Markdown -> Commit lên GitHub.

Workflow này giúp bạn duy trì blog cá nhân hoặc trang tài liệu (như trang này) mà không cần thao tác thủ công nhiều.

Yêu cầu chuẩn bị (Environment Variables)

Để workflow hoạt động trơn tru mà không cần sửa code bên trong các Node, bạn cần thiết lập các biến môi trường trong n8n.

Danh sách biến cần thiết

BiếnLevel áp dụngMô tảGiá trị mẫu
GITHUB_TOKEN1 & 2Personal Access Token (Scope: repo) để ghi file vào Github.ghp_KC7...
BLOG_REPO1 & 2Tên Repository chứa tài liệu.huynguyen/design-system-docs
POSTS_DIR1 & 2Đường dẫn thư mục lưu bài viết (tương ứng với category muốn đăng).
Lưu ý: Nên trỏ vào sub-folder cụ thể.
src/data/docs/n8n-automation
hoặc src/data/docs/general (cần tạo folder này trước nếu chưa có)
OPENAI_API_KEY2Key API của OpenAI để viết bài.sk-proj-...
OPENAI_MODEL2(Tùy chọn) Model AI sử dụng.gpt-4o-mini

Cách thêm biến môi trường vào n8n

Có 2 cách để thêm biến môi trường cho n8n:

Cách 1: Thêm vào file .env (Khuyên dùng)

Nếu bạn chạy n8n bằng Docker hoặc PM2, hãy thêm các dòng sau vào file cấu hình hoặc file .env:

bash:
# Github Config
export GITHUB_TOKEN="ghp_xxxxxx"
export BLOG_REPO="huynguyen/design-system-docs"

# Docs Project Structure
# Chọn 1 folder category cụ thể trong src/data/docs
export POSTS_DIR="src/data/docs/n8n-automation" 
# Hoặc tạo folder "general" và trỏ vào đó nếu muốn bài viết chung
# export POSTS_DIR="src/data/docs/general"

# AI Config
export OPENAI_API_KEY="sk-xxxxxx"
export OPENAI_MODEL="gpt-4o-mini"

Sau khi thêm, bạn cần khởi động lại n8n (pm2 restart n8n).

Cách 2: Setup Credential trực tiếp trong n8n (Dành cho bản Cloud/Desktop)

Nếu không can thiệp được vào biến môi trường hệ thống, bạn có thể tạo Credential loại "Predefined" trong n8n cho GitHub APIOpenAI API. Tuy nhiên, trong các workflow mẫu ở trên, chúng tôi đang dùng cú pháp $env.VAR_NAME. Nếu dùng Credential của n8n, bạn cần sửa lại Node HTTP Request:

  • Authentication: Chọn Predefined Credential Type -> GitHub API (hoặc OpenAI).
  • Xóa Header Authorization thủ công đi vì n8n sẽ tự động thêm.

Cấp độ 1: Tạo bài viết đơn lẻ (Basic)

Quy trình cơ bản để tạo một bài viết duy nhất từ một chủ đề.

Sơ đồ hoạt động

  1. Manual Trigger: Nhập Topic & Language.
  2. OpenAI: Viết bài trọn gói (JSON).
  3. GitHub: Commit file .md.

Code Workflow (JSON)

Copy đoạn JSON dưới đây và Import vào n8n:

json:
{
  "name": "Level 1: Hardcoded Post -> GitHub (Manual Config)",
  "nodes": [
    {
      "parameters": {},
      "id": "ManualTrigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [240, 240]
    },
    {
      "parameters": {
        "values": {
          "string": [
            { "name": "slug", "value": "hello-world-manual" },
            { "name": "title", "value": "Hello World Manual" },
            { "name": "description", "value": "Test commit from n8n without Env Vars." },
            { "name": "track", "value": "backend" },
            { "name": "content", "value": "## Success\nTest thành công với cấu hình thủ công." },
            { "name": "posts_dir", "value": "src/data/docs/n8n-automation" },
            { "name": "repo_name", "value": "huynv-dev/notes.huynguyen.info" },
            { "name": "github_token", "value": "dán_token_vào_đây" }
          ]
        },
        "options": {}
      },
      "id": "SetFixedData",
      "name": "Set Configuration",
      "type": "n8n-nodes-base.set",
      "typeVersion": 2,
      "position": [460, 240]
    },
    {
      "parameters": {
        "jsCode": "const data = $json;\n\n// Format Frontmatter\nconst fm = `--- \ntitle: \"${data.title}\"\ndescription: \"${data.description}\"\ntrack: \"${data.track || 'general'}\"\nlevel: \"Beginner\"\norder: 99\n---\n\n`;\nconst content = fm + data.content;\n\n// Encode Base64\nconst b64 = Buffer.from(content).toString('base64');\n\n// Path\nconst postsDir = data.posts_dir || 'src/data/docs/n8n-automation';\n\nreturn [{ json: { \n  slug: data.slug, \n  content_b64: b64, \n  filepath: `${postsDir}/${data.slug}.md`,\n  repo: data.repo_name,\n  token: data.github_token\n} }];"
      },
      "id": "BuildFile",
      "name": "Build Markdown",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [680, 240]
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "={{`https://api.github.com/repos/${$json.repo}/contents/${$json.filepath}`}}",
        "headerParameters": {
          "parameters": [
            { "name": "Authorization", "value": "={{'Bearer ' + $json.token}}" },
            { "name": "Accept", "value": "application/vnd.github+json" }
          ]
        },
        "sendBody": true,
        "jsonBody": "={{ { \"message\": \"chore(docs): add \" + $json.slug, \"content\": $json.content_b64 } }}",
        "options": {}
      },
      "id": "GitHub",
      "name": "Commit to GitHub",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [900, 240]
    }
  ],
  "connections": {
    "Manual Trigger": { "main": [[{ "node": "Set Configuration", "type": "main", "index": 0 }]] },
    "Set Configuration": { "main": [[{ "node": "Build Markdown", "type": "main", "index": 0 }]] },
    "Build Markdown": { "main": [[{ "node": "Commit to GitHub", "type": "main", "index": 0 }]] }
  }
}

Lưu ý quan trọng: Do n8n của bạn đang chặn truy cập biến môi trường ($env), workflow trên đã được chuyển sang chế độ Cấu hình Thủ công.

  1. Mở node "Set Configuration".
  2. Tìm trường github_token và dán trực tiếp Token của bạn vào đó (thay cho dán_token_vào_đây).
  3. Kiểm tra lại repo_name: Tôi đã tự động điền huynv-dev/notes.huynguyen.info (tên đúng của repo hiện tại).

🛠️ Troubleshooting (Sửa lỗi thường gặp)

Lỗi: Authorization failed

  • Kiểm tra nút Send Headers đã bật chưa.

Lỗi: 404 Not Found Đây là lỗi phổ biến nhất.

  1. Sai tên Repo: Bạn đang dùng huynguyen/design-system-docs (url cũ) nhưng GitHub của bạn lại là huynv-dev/notes.huynguyen.info. Hãy sửa lại trong node "Set Configuration".
  2. Sai Token Scope: Token phải có quyền repo (hoặc workflow nếu commit vào workflow file). Token Fine-grained cần cho phép truy cập đúng repo này.

Cấp độ 2: Tạo Series bài viết (Level 1 -> Level N)

Đây là bản nâng cấp giúp bạn tạo ra một chuỗi bài viết (Series) thay vì chỉ một bài. Ví dụ: "Học ReactJS" -> Tự động sinh ra "Bài 1: Cơ bản", "Bài 2: Component", "Bài 3: Hooks"...

Logic thay đổi

  1. AI Planner: Thay vì viết ngay, AI sẽ lên "Mục lục" (Syllabus) trước.
  2. Loop (Vòng lặp): Đi qua từng mục trong mục lục.
  3. AI Writer: Viết bài cho từng mục đang lặp.

Code Workflow (Loop Version)

json:
{
  "name": "Auto Blog - Series Generator",
  "nodes": [
    {
      "parameters": {},
      "id": "ManualTrigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [240, 240]
    },
    {
        "parameters": {
            "values": {
                "string": [
                    { "name": "topic", "value": "Python for Beginners" },
                    { "name": "levels", "value": "5" } 
                ]
            }
        },
        "id": "Config",
        "name": "Config Series",
        "type": "n8n-nodes-base.set",
        "position": [460, 240]
    },
    {
        "parameters": {
            "url": "https://api.openai.com/v1/chat/completions",
            "method": "POST",
            "headerParameters": {
                "parameters": [
                    { "name": "Authorization", "value": "={{'Bearer ' + $env.OPENAI_API_KEY}}" }
                ]
            },
            "sendBody": true,
            "jsonBody": "={\n  \"model\": \"gpt-4o-mini\",\n  \"messages\": [\n    { \"role\": \"system\", \"content\": \"Return JSON only.\" },\n    { \"role\": \"user\", \"content\": \"Create a \" + $json.levels + \"-part blog series on: '\" + $json.topic + \"'. Return JSON: { \\\"chapters\\\": [{ \\\"level\\\": 1, \\\"title\\\": \\\"...\\\" }] }\" }\n  ],\n  \"response_format\": { \"type\": \"json_object\" }\n}"
        },
        "id": "Planner",
        "name": "AI Planner",
        "type": "n8n-nodes-base.httpRequest",
        "position": [680, 240]
    },
    {
        "parameters": {
            "jsCode": "const chapters = JSON.parse($json.choices[0].message.content).chapters;\nreturn chapters.map(c => ({ json: { ...c, topic: $node['Config Series'].json.topic } }));"
        },
        "id": "ParsePlan",
        "name": "Parse Plan",
        "type": "n8n-nodes-base.code",
        "position": [900, 240]
    },
    {
        "parameters": {
            "batchSize": 1,
            "options": {}
        },
        "id": "Loop",
        "name": "Loop Levels",
        "type": "n8n-nodes-base.splitInBatches",
        "position": [1120, 240]
    },
    {
        "parameters": {
            "url": "https://api.openai.com/v1/chat/completions",
            "method": "POST",
            "headerParameters": {
                "parameters": [
                    { "name": "Authorization", "value": "={{'Bearer ' + $env.OPENAI_API_KEY}}" }
                ]
            },
            "sendBody": true,
            "jsonBody": "={\n  \"model\": \"gpt-4o-mini\",\n  \"messages\": [\n    { \"role\": \"system\", \"content\": \"You are a tech blogger.\" },\n    { \"role\": \"user\", \"content\": \"Write part \" + $json.level + \" of the series '\" + $json.topic + \"'. Title: \" + $json.title + \". Return JSON with fields: title, slug, content_md.\" }\n  ],\n  \"response_format\": { \"type\": \"json_object\" }\n}"
        },
        "id": "Writer",
        "name": "AI Writer (Item)",
        "type": "n8n-nodes-base.httpRequest",
        "position": [1340, 160]
    },
    {
        "parameters": {
            "url": "={{`https://api.github.com/repos/${$env.BLOG_REPO}/contents/content/posts/${$json.slug}.md`}}",
            "method": "PUT",
            "headerParameters": {
                "parameters": [
                    { "name": "Authorization", "value": "={{'Bearer ' + $env.GITHUB_TOKEN}}" }
                ]
            },
            "jsonBody": "={{ { \"message\": \"chore: add post \" + $json.slug, \"content\": Buffer.from($json.content_md).toString('base64') } }}"
        },
        "id": "Commit",
        "name": "GitHub Commit",
        "type": "n8n-nodes-base.httpRequest",
        "position": [1560, 160]
    }
  ],
  "connections": {
    "Manual Trigger": { "main": [[{ "node": "Config Series", "type": "main", "index": 0 }]] },
    "Config Series": { "main": [[{ "node": "AI Planner", "type": "main", "index": 0 }]] },
    "AI Planner": { "main": [[{ "node": "Parse Plan", "type": "main", "index": 0 }]] },
    "Parse Plan": { "main": [[{ "node": "Loop Levels", "type": "main", "index": 0 }]] },
    "Loop Levels": { "main": [[{ "node": "AI Writer", "type": "main", "index": 0 }]] },
    "AI Writer": { "main": [[{ "node": "GitHub Commit", "type": "main", "index": 0 }]] },
    "GitHub Commit": { "main": [[{ "node": "Loop Levels", "type": "main", "index": 0 }]] }
  }
}

Điểm lưu ý khi chạy Loop

  1. Rate Limit: Github và OpenAI đều có giới hạn request. Nếu tạo chuỗi > 10 bài, bạn nên thêm node Wait (10s) vào cuối vòng lặp.
  2. GitHub Conflict: Commit quá nhanh có thể gây lỗi git lock. Workflow này đã tối giản, trong sản xuất nên dùng cơ chế queue.
Quảng cáo
mdhorizontal