用 Next.js + Playwright 產生 1080×1080 的 Instagram carousel 圖片(封面 → 觀念 → 選擇題 → 解答 → CTA → 結尾),可直接透過 Graph API 發佈到 Instagram 和 Threads。
原本設計給 Python 教學用,但所有內容來自 src/data/carousel.json、品牌來自 src/data/brand.json,換主題只要改 JSON、換 logo,整個工作流可直接套用。
另附一個 Claude Code skill(.claude/skills/ig-carousel/SKILL.md),在 Claude Code 環境下說「幫我做一篇關於 XXX 的輪播」,Claude 會自動跑完「生內容 → 寫文案 → 截圖 → 發佈」整個流程。
- Slide types:
cover/explanation/content(選擇題)/answer/cta-question/end - 程式碼高亮:shiki,預設 Python,可改其他語言
- Playwright 截圖:逐張輸出 PNG 到
output/<topic-slug>/ - 一鍵發佈:
scripts/upload-ig.ts呼叫 Instagram Graph API,自動處理圖床暫存 + carousel 子容器 + 發佈 - Threads 發佈:流程文件在 skill 裡(
.claude/skills/ig-carousel/SKILL.mdStep 4-B) - 品牌抽離:所有品牌相關設定都在
src/data/brand.json,換人用只改一個檔
- Node.js 20+
- 一個 Instagram Business 或 Creator 帳號(個人帳號不能用 Content Publishing API)
- Meta Developers 帳號 + 一個 App(Business 類型)
- 想發 Threads 的話:把 IG 帳號連到 Threads、並在同個 App 加 Threads 產品
git clone <your-fork-url> ig-carousel
cd ig-carousel
npm install從 example 複製一份你自己的 brand.json(已被 .gitignore,不會把你的品牌值推回公開 repo):
cp src/data/brand.example.json src/data/brand.json然後編輯 src/data/brand.json:
然後把自己的 logo 和 QR code 放到 public/:
public/
logo.png # 透明背景 PNG(會同時用在封面、結尾、浮水印)
qrcode.png # 指向你主要社群連結的 QR code
想改主色、字體、版面?改
src/components/slides/*.tsx。品牌資料(username / logo 尺寸 / CTA 文字)改brand.json就好,不要硬寫在組件裡。
cp .env.example .env編輯 .env 填入 token(取得方式見下一節):
# Instagram(IG Login API,IGAA 開頭 token)
IG_ACCESS_TOKEN=IGAA...
IG_USER_ID=17841400000000000
# Threads(若要發 Threads)
THREADS_ACCESS_TOKEN=THAA...
THREADS_USER_ID=12345000000000000.env 已被 .gitignore,不會被 commit。
-
前往 https://developers.facebook.com/apps,建立 Business 類型 App
-
左側 Products → Instagram → API setup with Instagram Login
-
在 1. Generate access tokens 區塊加入你的 IG 帳號並產生 token
- 需要權限:
instagram_business_basic、instagram_business_content_publish
- 需要權限:
-
複製產生的 long-lived token(
IGAA開頭)到.env的IG_ACCESS_TOKEN -
取得
IG_USER_ID:curl "https://graph.instagram.com/v21.0/me?fields=id,username&access_token=$IG_ACCESS_TOKEN"回傳
{"id":"27020...","username":"..."},把id填到.env。
⚠️ Token 60 天會過期。 過期前可以 refresh:curl "https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=$IG_ACCESS_TOKEN"過期後沒救,要重開後台重新產。建議變成月常規。
-
同一個 App 加 Threads 產品
-
Threads → API Setup 產生 long-lived user token
-
取得
THREADS_USER_ID:curl "https://graph.threads.net/v1.0/me?fields=id,username&access_token=$THREADS_ACCESS_TOKEN"
⚠️ IG user id 跟 Threads user id 不一樣(就算是同一個 username 也一樣),不能混用。.env裡一定要分開放。
(或用 Claude Code skill 自動生)
{
"topic": "your-topic-slug",
"slides": [
{ "type": "cover", "title": "...", "subtitle": "...", "tags": ["...", "..."] },
{ "type": "explanation", "title": "...", "body": "...", "points": ["..."], "code": { "language": "python", "content": "..." } },
{ "type": "content", "title": "...", "code": {...}, "question": "...", "options": ["A. ...", "B. ...", "C. ...", "D. ..."] },
{ "type": "answer", "title": "答案:B", "explanation": "...", "points": ["..."] },
{ "type": "cta-question", "title": "...", "code": {...}, "question": "...", "options": [...], "ctaText": "私訊回覆「答案」獲得解答" },
{ "type": "end" }
]
}npm run dev打開 http://localhost:3000/carousel 預覽全部頁面。
npx tsx scripts/capture.tsPNG 會輸出到 output/<topic-slug>/,檔名依序為 01-cover.png、02-explanation.png、…。
在 output/<topic-slug>/caption.txt 寫好文案(IG / Threads 共用)。
⚠️ Threads 限制 500 字,IG 限制 2200 字。 文案結構建議:鉤子 → 內容摘要 → CTA → 分隔符 → hashtags。Hashtag 區塊前務必加一個非空白字元(例如
—)當分隔符——Threads API 實測會把「貼文開頭接 newline 的第一個 hashtag」的井字號吃掉。
# 只建容器(24h 內可發佈)
npx tsx scripts/upload-ig.ts
# 建容器 + 立即發佈
npx tsx scripts/upload-ig.ts --publish
# 發佈之前建好的容器
npx tsx scripts/upload-ig.ts --publish <container_id>腳本會把 output/<topic-slug>/*.png 先上傳到 litterbox.catbox.moe(72h 暫存)供 IG 伺服器抓取,然後建 carousel item → carousel container → publish。
目前沒有腳本,流程在 .claude/skills/ig-carousel/SKILL.md Step 4-B(手動 curl 流程)。核心差異:
- Base URL 是
https://graph.threads.net/v1.0 - 建 item 時
media_type=IMAGE必填 - 建 carousel container 時文案欄位叫
text,不是caption
本 repo 包含一個 Claude Code skill:.claude/skills/ig-carousel/SKILL.md。
在 Claude Code 開啟本專案後,說:
「幫我做一張關於 Python list comprehension 的 carousel,封面標題 XXX,大綱 YYY ZZZ,發到 IG 和 Threads」
Claude 會自動:
- 生
src/data/carousel.json - 寫
caption.txt - 啟 dev server + Playwright 截圖
- 把 carousel 發到 IG / Threads
整個流程的 prompt、踩雷提醒、token 驗證指令都在 skill 裡,fork 後不用再調教。
| 症狀 | 可能原因 / 解法 |
|---|---|
Object with ID 'xxx' does not exist |
IG_USER_ID 跟 THREADS_USER_ID 用混了,或 token 對不到 user id |
Invalid OAuth access token / 401 |
Token 過期(60 天),重開 Meta 後台產新的 |
upload-ig.ts 上傳到 litterbox 時 HTTP 429 |
Rate limit。手動用 tmpfiles.org 上傳剩下的圖、手動跑剩下流程(見 skill Step 4-A 踩雷提醒) |
Threads 文案第一個 #hashtag 變純文字 |
已知 quirk:hashtag 區塊前要加一個非空白字元當分隔符 |
Media aspect ratio must be within... |
圖片比例不在 4:5 ~ 1.91:1,檢查 slide 組件尺寸 |
| 截圖 fails / 空白 | dev server 沒啟、或 port 不是 3000 |
.
├── .claude/skills/ig-carousel/SKILL.md # Claude Code skill(核心 prompt)
├── public/ # logo.png、qrcode.png(自行替換)
├── scripts/
│ ├── capture.ts # Playwright 截圖
│ └── upload-ig.ts # IG 發佈
├── src/
│ ├── app/carousel/page.tsx # 輪播預覽頁
│ ├── components/slides/ # 各類 slide 組件
│ └── data/
│ ├── brand.json # 品牌設定(改這裡)
│ └── carousel.json # 當前輪播內容
├── output/<topic-slug>/ # 截圖 + caption 輸出(gitignored)
└── .env.example # 環境變數範本
.env、output/、scripts/ig_session*.json都在.gitignore,不會被 commit- Token 請不要 commit,也不要貼到 issue / PR
- Token 洩漏請立刻到 Meta Developers 後台 invalidate
MIT. 隨便改隨便用,但發佈到的社群帳號的內容請自行負責。
{ "name": "你的品牌名", "logo": { "src": "/logo.png", "alt": "你的品牌", "coverWidth": 360, // 封面頁 logo 尺寸 "coverHeight": 99, "endWidth": 300, // 結尾頁 logo 尺寸 "endHeight": 83 }, "qrcode": { "src": "/qrcode.png", "width": 200, "height": 200 }, "social": { "instagram": "your_ig_username", "threads": "your_threads_username" }, "endSlide": { "ctaTitle": "追蹤更多教學" } }