Tidebase Tidebase GitHub Start self-hosting
Docs

How to run durable AI workflows behind a Next.js route

Don’t run a five-minute agent workflow inside a request handler — enqueue a durable Tidebase run from your route, return the runId, and let a worker execute it. Next.js App Router handlers speak web Request/Response, so Tidebase’s recovery webhook handler mounts as a route export with no adapter at all.

// app/api/reports/route.ts — enqueue, don't execute
import { Tidebase } from '@tidebase/sdk'

const tide = new Tidebase()

export async function POST(request: Request) {
  const { topic } = await request.json()
  const { run } = await tide.enqueue('generate-report', {
    input: { topic },
    dedupeKey: `report-${topic}`
  })
  return Response.json({ runId: run.id })
}
// app/api/tidebase/route.ts — recovery webhook, zero adapter
import { Tidebase } from '@tidebase/sdk'
import { generateReport } from '@/workflows/generate-report'

const tide = new Tidebase()
tide.workflow('generate-report', generateReport)

export const POST = tide.webhook()

Tidebase is an open-source checkpoint layer for AI agents: wrap your steps, and failed runs resume from the last safe point — in your own Postgres, without moving execution into a new runtime.

The shape: enqueue → worker → state

  1. The route enqueues. tide.enqueue creates a durable queued run in Postgres — with dedupe, so a double-submitted form doesn’t create two reports — and returns immediately. Your handler stays inside any serverless time limit because it does no agent work.
  2. A worker executes. A long-lived process (not a serverless function) runs tide.work({ queues: ['default'] }) and executes registered workflows off the queue, with retries and backoff handled by Tidebase. This is the honest tradeoff stated plainly: Tidebase does not execute your code — you run this worker. On most Next.js hosting that means a small companion process or container, not a route.
  3. The UI reads run state. Server components or route handlers read progress with tide.runs.get(runId) — live state, step results, gate status — straight from your Postgres. Workflows update it with run.state.patch({ progress }) as they go.

Recovery and the webhook route

If a worker dies mid-run, the lease expires and Tidebase’s reconciler requeues queue runs automatically. For runs created outside queues, mount the recovery webhook (above): Tidebase POSTs a signed run.resume payload, the SDK verifies the HMAC (TIDEBASE_WEBHOOK_SECRET on both sides) and re-invokes the registered workflow with the same runId — completed steps replay from checkpoints per the replay contract.

One Next.js-specific caveat: the webhook re-runs the remaining workflow inside the route invocation, so it inherits your function’s execution limits. If your unfinished tail can be long, prefer queues (the reconciler requeues without needing the webhook) or have the webhook route enqueue instead of executing inline.

Approval gates in your product UI

A workflow paused at run.gate('approve-send', ...) shows up in runs.get with a pending gate. Render it in your app and resolve it from a route handler or server action — the gate’s webhook channel payload includes a resolveUrl and resolveToken. The decision is durable, exactly-once, and recorded with the actor. See human approval gates.

What Tidebase does not do here

  • It is not a Next.js background-function provider. It stores run truth and decides when code should run (queues, cron, recovery); the compute is yours.
  • It does not stream tokens. Stream LLM output to the browser however you do today; checkpoint completed steps.
  • Alpha, opt-in auth. Set TIDEBASE_API_KEY (and the webhook secret) before pointing production traffic at it.

Repo: https://github.com/BlueprintLabIO/tidebase · See also: Queues, schedules, and cancellation