使用 Next.js、Supabase 和 Cron 在 2026 年構建代理驅動應用程式
在 2026 年用 Next.js、Supabase 和 Cron 構建代理驅動的應用程式
在過去的半年裡,我沉浸於為客戶構建三個代理驅動的應用程式。其中兩個已完全上線,每天自主處理數千個任務。那麼,是什麼為這些項目提供動力呢?是部署在 Vercel 上的強大 Next.js、用於持久化和實時需求的 Supabase,以及可靠的基於 cron 的編排來保持代理協調並防止過度的 API 費用。這不僅僅是理論——我將分析什麼有效、陷阱以及我在開始前希望能了解的所有見解。

2026 年代理驅動的應用程式實際上是什麼樣子
當我談論「代理驅動的應用程式」時,我並不只是在討論一個重複系統提示的聊天機器人。想想那些自主處理多步驟任務、做出實時數據驅動決策、按計劃運行的軟體,所有這些都無需人為推動。
以下是在生產中運行的一個示例:
- 內容管道掃描 RSS 源、生成摘要、起草社交帖子並將其排隊以供審核。此週期每 30 分鐘重複一次。
- 數據協調代理每晚連接到多個 API,嗅出不一致之處,嘗試自動修復,並將無法解決的問題上報。
- 客戶入職代理由新的註冊 webhook 觸發。這些代理配置資源、發送自定義序列,並隨著用戶行為的學習而適應。
共同的主題?他們需要狀態持久化、計劃執行、工具訪問以及人類窺視和干預的方式。這正是我們要深入探討的堆棧的最佳點。
為什麼這個堆棧有效
我已經嘗試過其他設置。有與 Python 後端配對的 LangChain、用於編排的 Temporal 和自定義 AWS 隊列系統。雖然他們都完成工作,但他們帶來了大量的操作複雜性,大多數團隊到 2026 年根本無法處理。
以下是我直接的比較:
| 堆棧 | 設置時間 | 操作開銷 | 10K 任務/天的成本 | 最適合 |
|---|---|---|---|---|
| Next.js + Supabase + Vercel Cron | 1-2 天 | 低 | ~$85-150/月 | 大多數代理應用 |
| Python + Celery + Redis + AWS | 3-5 天 | 高 | ~$200-400/月 | 計算密集型任務 |
| Temporal + 自定義 Workers | 5-10 天 | 很高 | ~$300-600/月 | 複雜工作流 |
| Inngest + Next.js | 1-2 天 | 低-中 | ~$100-200/月 | 事件驅動的代理 |
Next.js 組合在速度上確實勝出。您可以獲得無服務器功能、儀表板 UI、數據庫、身份驗證、實時更新和定時執行,而無需擔心基礎設施。對於我們大多數人構建代理應用程式,這綽綽有餘。
架構概覽
讓我們描繪一個在各個項目中大獲成功的架構圖景:
┌─────────────────┐ ┌──────────────────┐
│ Vercel Cron │────▶│ Next.js API Route │
│ (Scheduler) │ │ (Agent Runner) │
└─────────────────┘ └────────┬───────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌───────────┐
│ Supabase │ │ LLM API │ │ External │
│ (State + │ │ (OpenAI/│ │ Tools & │
│ Queue) │ │ Claude) │ │ APIs │
└──────────┘ └─────────┘ └───────────┘
│
▼
┌──────────┐
│ Supabase │
│ Realtime │──▶ Dashboard UI
└──────────┘
cron 作業調用 Next.js API 路由,然後從 Supabase 提取任務,執行代理邏輯,寫回結果並完成。同時,您的儀表板正在通過 Supabase Realtime 提供實時更新來關注行動。
是的,夠簡單,但魔鬼就在細節中。

使用 Next.js 和 Vercel 設置基礎
當您在 2026 年構建代理應用程式時,帶有 App Router 的 Next.js 15 是必須的。由於其 Server Actions 和 Route Handlers,它為您提供了最乾淨的執行模式。在 Social Animal,我們已經磨練了這項 Next.js 技能,讓我告訴您,App Router 對於後端密集型項目已經成熟得非常好。
啟動項目很簡單:
pnpx create-next-app@latest agent-app --typescript --tailwind --app
cd agent-app
pnpm add @supabase/supabase-js ai @ai-sdk/openai
我正在利用 Vercel AI SDK,因為它在不同 LLM 提供商之間提供統一的界面。到 2026 年,ai 包的版本 4.x 支持結構化輸出、工具調用和開箱即用的流式傳輸——LangChain 誰?
現在您的 vercel.json 需要看起來像這樣的 cron 配置:
{
"crons": [
{
"path": "/api/agents/run",
"schedule": "*/15 * * * *"
},
{
"path": "/api/agents/cleanup",
"schedule": "0 2 * * *"
}
]
}
第一個 cron 每 15 分鐘運行一次代理。第二個每晚清理一次。記住,Vercel Pro($20/月)支持頻率高達 1 分鐘的 cron 作業。但如果您在免費計劃上,您限制為每日 cron,這對大多數代理驅動的需求來說根本不夠。
Vercel 功能配置
代理任務可能比暑期大片還長(好吧,不完全是),但默認情況下需要超過 10 秒才能執行。配置您的路由如下:
// app/api/agents/run/route.ts
export const maxDuration = 300; // Pro 計劃上 5 分鐘
export const dynamic = 'force-dynamic';
export async function GET(request: Request) {
// 驗證 cron 密鑰以防止未經授權的訪問
const authHeader = request.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response('Unauthorized', { status: 401 });
}
// 代理邏輯在這裡
return Response.json({ success: true });
}
那個 maxDuration?這很關鍵。Pro 上最多 300 秒,如果您在企業水域中暢遊,則 900 秒。任何例行任務超過 5 分鐘?是時候分段它們或重新考慮您的執行模型了。
Supabase 作為您代理的大腦
Supabase——一個強大的工具。它充當您的任務隊列、狀態存儲、審計日誌和實時更新通知器。以下是我長期調整過的模式:
-- 代理任務隊列
CREATE TABLE agent_tasks (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
agent_type TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'running', 'completed', 'failed', 'cancelled')),
priority INTEGER DEFAULT 0,
payload JSONB NOT NULL DEFAULT '{}',
result JSONB,
error TEXT,
attempts INTEGER DEFAULT 0,
max_attempts INTEGER DEFAULT 3,
locked_until TIMESTAMPTZ,
scheduled_for TIMESTAMPTZ DEFAULT NOW(),
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 隊列輪詢查詢的索引
CREATE INDEX idx_agent_tasks_queue ON agent_tasks (
agent_type, status, priority DESC, scheduled_for
) WHERE status = 'pending';
-- 用於可觀測性的代理執行日誌
CREATE TABLE agent_logs (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
task_id UUID REFERENCES agent_tasks(id),
level TEXT NOT NULL DEFAULT 'info',
message TEXT NOT NULL,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 代理狀態持久化(用於多步驟代理)
CREATE TABLE agent_state (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
agent_type TEXT NOT NULL,
agent_instance_id TEXT NOT NULL,
state JSONB NOT NULL DEFAULT '{}',
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(agent_type, agent_instance_id)
);
那 locked_until 列?這很關鍵。它模擬了一個臨時分佈式鎖。這意味著當 cron 作業選擇任務時,它會將 locked_until 設置為未來 5 分鐘。如果進程崩潰,鎖過期,另一個週期可以再次嘗試。
任務拾取查詢
王冠上的寶石。這是您無需重新處理即可搶奪任務的金票:
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
async function claimTasks(agentType: string, limit: number = 5) {
const { data: tasks, error } = await supabase
.rpc('claim_agent_tasks', {
p_agent_type: agentType,
p_limit: limit,
p_lock_duration: '5 minutes'
});
if (error) throw error;
return tasks;
}
以及匹配的 Postgres 函數:
CREATE OR REPLACE FUNCTION claim_agent_tasks(
p_agent_type TEXT,
p_limit INTEGER,
p_lock_duration INTERVAL
)
RETURNS SETOF agent_tasks AS $$
BEGIN
RETURN QUERY
UPDATE agent_tasks
SET
status = 'running',
locked_until = NOW() + p_lock_duration,
started_at = NOW(),
attempts = attempts + 1,
updated_at = NOW()
WHERE id IN (
SELECT id FROM agent_tasks
WHERE agent_type = p_agent_type
AND status = 'pending'
AND scheduled_for <= NOW()
AND (locked_until IS NULL OR locked_until < NOW())
AND attempts < max_attempts
ORDER BY priority DESC, scheduled_for ASC
LIMIT p_limit
FOR UPDATE SKIP LOCKED
)
RETURNING *;
END;
$$ LANGUAGE plpgsql;
FOR UPDATE SKIP LOCKED 在這裡是無名英雄。它確保沒有兩個並發調用踩到彼此的腳趾。相信我,發現和修復雙重處理並不有趣。
基於 Cron 的代理編排
Vercel Cron 是這整個操作的穩定鼓聲。每 15 分鐘,您的代理運行器就會啟動,抓取任務,處理它們,然後休眠直到下一次調用。
以下是完整代理運行器路由的外觀:
// app/api/agents/run/route.ts
import { claimTasks, completeTask, failTask, logAgent } from '@/lib/agents';
import { runContentAgent } from '@/lib/agents/content';
import { runReconciliationAgent } from '@/lib/agents/reconciliation';
export const maxDuration = 300;
const AGENT_RUNNERS: Record<string, (task: AgentTask) => Promise<any>> = {
'content-pipeline': runContentAgent,
'data-reconciliation': runReconciliationAgent,
};
export async function GET(request: Request) {
const authHeader = request.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response('Unauthorized', { status: 401 });
}
const results = { processed: 0, failed: 0, skipped: 0 };
for (const [agentType, runner] of Object.entries(AGENT_RUNNERS)) {
const tasks = await claimTasks(agentType, 5);
for (const task of tasks) {
try {
await logAgent(task.id, 'info', `Starting ${agentType} task`);
const result = await runner(task);
await completeTask(task.id, result);
results.processed++;
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
await failTask(task.id, message);
await logAgent(task.id, 'error', message);
results.failed++;
}
}
}
return Response.json(results);
}
注意每個代理類型的順序處理,每次運行限制為 5 個任務。這有助於保持函數執行時間。有 50 個待處理任務的堆積?將它們分散到多個 cron 週期中。沒有急事,沒有問題。
當 Cron 不夠時
有時,生活來得更快,cron 根本不夠。在這些情況下,我將 cron 與 Supabase 數據庫 Webhooks 或 Edge Functions 集成——它們由數據庫更改觸發:
-- 當創建高優先級任務時觸發邊界函數
CREATE OR REPLACE FUNCTION notify_urgent_task()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.priority > 5 THEN
PERFORM net.http_post(
url := 'https://your-app.vercel.app/api/agents/urgent',
headers := jsonb_build_object(
'Authorization', 'Bearer ' || current_setting('app.webhook_secret')
),
body := jsonb_build_object('task_id', NEW.id)
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
此設置允許您混合可預測的基於 cron 的處理與緊急任務的快速響應。
構建代理循環
這就是魔法發生的地方。使用 Vercel AI SDK 的工具調用的代理實現示例:
// lib/agents/content.ts
import { generateText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
import { supabase } from '@/lib/supabase';
export async function runContentAgent(task: AgentTask) {
const { sourceUrl, targetPlatform } = task.payload;
const { text, toolCalls, usage } = await generateText({
model: openai('gpt-4o-mini'),
system: `You are a content processing agent. Fetch the source content,
analyze it, and generate appropriate social media content for ${targetPlatform}.
Use the provided tools to complete your task.`,
prompt: `Process this source: ${sourceUrl}`,
tools: {
fetchContent: tool({
description: 'Fetch content from a URL',
parameters: z.object({ url: z.string().url() }),
execute: async ({ url }) => {
const res = await fetch(url);
const html = await res.text();
// Strip HTML, extract main content
return extractMainContent(html);
},
}),
saveContent: tool({
description: 'Save generated content for review',
parameters: z.object({
title: z.string(),
body: z.string(),
platform: z.string(),
suggestedPostTime: z.string(),
}),
execute: async (content) => {
const { data, error } = await supabase
.from('generated_content')
.insert({
...content,
task_id: task.id,
status: 'pending_review',
})
.select()
.single();
if (error) throw error;
return { saved: true, id: data.id };
},
}),
},
maxSteps: 5, // 允許最多 5 輪工具調用
});
return {
summary: text,
toolCallCount: toolCalls.length,
tokensUsed: usage.totalTokens,
};
}
將 maxSteps 設置為 5 允許 LLM 調用工具、評估結果並相應地處理——就像一個小工蜂在做它的事情。大多數內容任務在 3-4 個這樣的調用中完成。
使用 Supabase Realtime 進行實時代理監控
自主代理很棒,但對它們保持監控至關重要。Supabase Realtime 為儀表板提供支持,該儀表板在代理完成任務時實時更新:
// components/AgentDashboard.tsx
'use client';
import { useEffect, useState } from 'react';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
export function AgentDashboard() {
const [tasks, setTasks] = useState<AgentTask[]>([]);
const [logs, setLogs] = useState<AgentLog[]>([]);
useEffect(() => {
// 訂閱任務更改
const taskChannel = supabase
.channel('agent-tasks')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'agent_tasks' },
(payload) => {
setTasks(prev => {
const updated = [...prev];
const idx = updated.findIndex(t => t.id === payload.new.id);
if (idx >= 0) updated[idx] = payload.new as AgentTask;
else updated.unshift(payload.new as AgentTask);
return updated.slice(0, 50);
});
}
)
.subscribe();
// 訂閱日誌流
const logChannel = supabase
.channel('agent-logs')
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'agent_logs' },
(payload) => {
setLogs(prev => [payload.new as AgentLog, ...prev].slice(0, 100));
}
)
.subscribe();
return () => {
supabase.removeChannel(taskChannel);
supabase.removeChannel(logChannel);
};
}, []);
// 渲染您的儀表板 UI...
}
這提供了代理活動的實時流。通常,我會添加指標、任務計數、錯誤率視覺效果,甚至一個「立即運行」按鈕用於測試。Supabase Realtime 包含在免費層中——不需要額外費用。
處理故障和邊界情況
為此做好準備——某些東西將爆炸。API 不見了,速率限制擊中你,或服務只是回送無意義的結果。以下是我學到的有關製造存活系統的內容。
指數退避重試
我們整合的 attempts 和 max_attempts 列處理重試。如果任務失敗,它會以「已失敗」狀態停留,但可以稍後手動或自動設置為重試:
async function failTask(taskId: string, error: string) {
const { data } = await supabase
.from('agent_tasks')
.select('attempts, max_attempts')
.eq('id', taskId)
.single();
const shouldRetry = data && data.attempts < data.max_attempts;
await supabase
.from('agent_tasks')
.update({
status: shouldRetry ? 'pending' : 'failed',
error,
locked_until: null,
// 指數退避:1 分鐘、4 分鐘、9 分鐘...
scheduled_for: shouldRetry
? new Date(Date.now() + Math.pow(data.attempts, 2) * 60000).toISOString()
: undefined,
updated_at: new Date().toISOString(),
})
.eq('id', taskId);
}
外部 API 的熔斷器
當外部 API 出現故障時,立即重試會快速耗盡重試。在 Supabase 中追蹤故障率,如果情況看起來不妙,您可以暫停代理:
async function checkCircuitBreaker(service: string): Promise<boolean> {
const fiveMinutesAgo = new Date(Date.now() - 300000).toISOString();
const { count } = await supabase
.from('agent_logs')
.select('*', { count: 'exact', head: true })
.eq('level', 'error')
.ilike('message', `%${service}%`)
.gte('created_at', fiveMinutesAgo);
return (count ?? 0) < 10; // 如果 5 分鐘內有超過 10 個錯誤,則跳閘
}
成本分析和優化
現在我們來談談關鍵問題。在 2026 年運行生產代理應用程式需要多少成本?
| 組件 | 免費層 | 典型生產 | 重度使用 |
|---|---|---|---|
| Vercel Pro | - | $20/月 | $20/月 + 使用費 |
| Supabase Pro | - | $25/月 | $25/月 + 使用費 |
| OpenAI (GPT-4o-mini) | - | $30-80/月 | $200+/月 |
| Anthropic (Claude 3.5 Haiku) | - | $20-50/月 | $150+/月 |
| 總計 | ~$0(開發) | $95-175/月 | $395+/月 |
那些 LLM 費用?它們主導一切。以下是您如何對其保持控制:
- 盡可能使用較小的模型。 GPT-4o-mini 和 Claude 3.5 Haiku 以更大堂兄弟成本的一小部分處理大多數任務。
- 緩存後再調用 API。 Supabase 可以存儲 LLM 響應,減少新的 API 調用。
- 批量、批量、批量。 不是每個任務一個 API 調用,而是批量相似的任務並一起發送。
- 令牌限制?是的,請。 始終在您的 AI SDK 調用中設置
maxTokens——防止肆無忌憚的生成。
生產部署模式
一旦您為部署做準備,幾件事很關鍵:
環境分離。 利用 Supabase 分支(需要 Pro)為測試和生產維持不同的數據庫。將 Vercel 預覽部署與 Supabase 分支配對以實現完全隔離。
監控。 Vercel 的可觀測性提供 cron 執行歷史記錄和函數日誌。將其與 Supabase 運行狀況檢查配對:
SELECT
agent_type,
status,
COUNT(*) as count,
AVG(EXTRACT(EPOCH FROM (completed_at - started_at))) as avg_duration_seconds
FROM agent_tasks
WHERE created_at > NOW() - INTERVAL '24 hours'
GROUP BY agent_type, status
ORDER BY agent_type, status;
秘密管理。 使用 Vercel 環境變量保護您的 API 密鑰,永遠不要對其進行硬編碼。僅在服務器端使用 Supabase 的服務角色密鑰,同時在客戶端保留匿名密鑰。
如果您需要幫助設置這樣的系統,我們的團隊已使用完全相同的堆棧交付了多個代理驅動的應用程式。查看我們的無頭 CMS 開發和Next.js 開發能力,或聯繫我們討論您的項目。
常見問題
Vercel Cron 能夠處理高頻代理任務嗎?
Vercel Cron 可以管理 Pro 計劃上短至 1 分鐘的間隔。大多數代理任務適合 5-15 分鐘週期。需要更快?考慮 Supabase 數據庫 Webhooks 或切換到 Inngest 進行事件驅動的操作。
我如何使用 cron 防止重複任務處理?
那就是 PostgreSQL 在您的任務聲明查詢中閃耀的 FOR UPDATE SKIP LOCKED。這是您的安全網,即使 cron 時序重疊,確保每個任務都是唯一聲明的。如果發生中途執行災難,locked_until 增加了一層安全性。
Supabase 對生產代理工作負載可靠嗎?
絕對。Supabase Pro 在帶有自動備份和 99.9% 正常運行時間 SLA 的專用 Postgres 實例上運行。實際上,對於此任務隊列系統,Postgres 是完全合格的——能夠輕鬆處理並發訪問和複雜查詢。
在生產中運行 AI 代理需要多少成本?
通常,每天處理 5,000-10,000 個任務將花費您每月 $95-175(Vercel Pro + Supabase Pro)加上使用 GPT-4o-mini 等模型每月 $30-80 的 LLM API 調用。變數層是您的 LLM 成本——取決於任務複雜性和令牌使用。
我應該為代理邏輯使用 LangChain 還是 Vercel AI SDK?
到 2026 年,Vercel 的 AI SDK(v4.x)本地覆蓋大多數代理需求——工具使用、多步驟推理、結構化輸出和流式傳輸。LangChain 在您深入研究高級思想鏈模式、RAG 設置或專門工具集成時變得有用。對於此堆棧,使用 AI SDK 減少複雜性至上。
我可以改用 Astro 而不是 Next.js 來實現此模式嗎?
Astro 可以處理 cron 觸發的 API 路由,但您會錯過 Vercel 的優化,包括 cron 配置和更長的超時。如果您的儀表板更喜歡內容繁重、低互動性,Astro 適合前端。為您的代理後端堅持 Next.js。
我如何監控代理性能和故障?
混合三層:Vercel 日誌以獲得執行級別的洞察、Supabase 中的 agent_logs 表以進行應用程式日誌跟蹤,以及 Supabase Realtime 以進行動態儀表板監控。為故障、執行時間和隊列長度建立運行狀況檢查。Vercel 無縫同步到 PagerDuty 和 Slack 等警報服務。
當我的代理任務超過 Vercel 的函數超時時會發生什麼?
在 Vercel Pro 上,您有 300 秒的上限。如果任務經常超過此限制,請考慮將它們分解為跨 cron 週期的小任務。例如,將內容流程分為「獲取」、「分析」和「生成」任務。或者,探索 Supabase Edge Functions 以延長執行——它們有 150 秒的限制,可以根據需要觸發。