在 2026 年用 Next.js、Supabase 和 Cron 構建代理驅動的應用程式

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

Building Agent-Powered Apps with Next.js, Supabase & Cron in 2026

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 提供實時更新來關注行動。

是的,夠簡單,但魔鬼就在細節中。

Building Agent-Powered Apps with Next.js, Supabase & Cron in 2026 - architecture

使用 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 不見了,速率限制擊中你,或服務只是回送無意義的結果。以下是我學到的有關製造存活系統的內容。

指數退避重試

我們整合的 attemptsmax_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 費用?它們主導一切。以下是您如何對其保持控制:

  1. 盡可能使用較小的模型。 GPT-4o-mini 和 Claude 3.5 Haiku 以更大堂兄弟成本的一小部分處理大多數任務。
  2. 緩存後再調用 API。 Supabase 可以存儲 LLM 響應,減少新的 API 調用。
  3. 批量、批量、批量。 不是每個任務一個 API 調用,而是批量相似的任務並一起發送。
  4. 令牌限制?是的,請。 始終在您的 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 秒的限制,可以根據需要觸發。