|
| 1 | +export const author = "nicholas-kissel" |
| 2 | +export const published = "2025-10-17" |
| 3 | +export const category = "changelog" |
| 4 | +export const keywords = ["vercel", "rivet", "actors", "durable-objects", "open-source", "serverless", "launch"] |
| 5 | + |
| 6 | +# Rivet Actors on Vercel Functions: Open-Source Alternative to Durable Objects |
| 7 | + |
| 8 | +**Rivet Actors can now run on Vercel Functions, bringing stateful, realtime workloads to Vercel's serverless platform.** |
| 9 | + |
| 10 | +Cloudflare's Durable Objects introduced a long-lived, stateful primitive in serverless apps – but they come with platform lock-in and resource constraints. Rivet Actors offer an open-source alternative, and by launching on Vercel we unlock better flexibility, control, and developer experience. |
| 11 | + |
| 12 | +## How Vercel + Rivet Compares To Durable Objects |
| 13 | + |
| 14 | +| Dimension | Rivet Actors on Vercel Functions | Cloudflare Durable Objects | Why it matters | |
| 15 | +|--------------------------------|---------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------| |
| 16 | +| **Runtime** | **Standard Node.js** (Vercel Functions), full support with npm packages | **Custom runtime** (workerd), subset of Node.js APIs, partial support with npm packages | Using standard Node.js makes packages and frameworks "just work" and reduces vendor lock-in. | |
| 17 | +| **Memory / CPU per actor** | **Configurable** up to 4 GB / 2 vCPU | Per-isolate memory cap **128 MB** | Memory-heavy workloads are more feasible on Vercel than on Cloudflare | |
| 18 | +| **Regional control** | **Configurable** specific regions | DOs can be restricted to broad jurisdictions and accept location hints, though **limited control** | Explicit control helps reduce latency and meet compliance requirements | |
| 19 | +| **Lock-in / portability** | **Open-source actor library** designed to be portable across standard runtimes/clouds | DOs run on Cloudflare's runtime and APIs, **not open-source, not portable** | Open source + standard runtimes provide flexibility, enables migrations, and allows for on-prem deployments | |
| 20 | + |
| 21 | +## What Are Rivet Actors? |
| 22 | + |
| 23 | +Similar to Durable Objects, Rivet Actors provide long-lived, stateful actors with **storage, real-time (WebSockets/SSE), and hibernation**. However, unlike Durable Objects, Rivet is open-source and portable — you can self-host or run on any platform. |
| 24 | + |
| 25 | +- **Long-Lived, Stateful Compute**: Each unit of compute is like a tiny server that remembers things between requests – no need to re-fetch data from a database or worry about timeouts. Like AWS Lambda, but with persistent memory between invocations and no timeouts. |
| 26 | + |
| 27 | +- **Realtime, Made Simple**: Update state and broadcast changes in realtime with WebSockets or SSE. No external pub/sub systems, no polling – just built-in low-latency events. |
| 28 | + |
| 29 | +- **No Database Round Trips**: State is stored on the same machine as your compute so reads and writes are ultra-fast. No database round trips, no latency spikes. |
| 30 | + |
| 31 | +- **Sleep When Idle, No Cold Starts**: Actors automatically hibernate when idle and wake up instantly on demand with zero cold start delays. Only pay for active compute time. |
| 32 | + |
| 33 | +- **Architected For Insane Scale**: Automatically scale from zero to millions of concurrent actors. Pay only for what you use with instant scaling and no cold starts. |
| 34 | + |
| 35 | +- **No Vendor Lock-In**: Open-source and fully self-hostable. |
| 36 | + |
| 37 | +## Quick Example: Building an AI Agent with Rivet Actors |
| 38 | + |
| 39 | +Here's how simple it is to build a stateful AI agent using Rivet Actors on Vercel. This example demonstrates an AI chatbot that maintains conversation history, processes messages with OpenAI, and broadcasts updates to all connected clients in real-time – all without managing databases or WebSocket infrastructure. |
| 40 | + |
| 41 | +<CodeGroup> |
| 42 | + |
| 43 | +```typescript {{"title":"src/app/rivet/registry.ts"}} |
| 44 | +import { actor, setup } from "rivetkit"; |
| 45 | +import { openai } from "@ai-sdk/openai"; |
| 46 | +import { generateText, tool } from "ai"; |
| 47 | +import { z } from "zod"; |
| 48 | +import { type Message, getWeather } from "./utils"; |
| 49 | + |
| 50 | +// Create an actor for every agent |
| 51 | +export const aiAgent = actor({ |
| 52 | + // Persistent state that survives restarts |
| 53 | + state: { |
| 54 | + messages: [] as Message[], |
| 55 | + }, |
| 56 | + |
| 57 | + // Actions are callable by your frontend or backend |
| 58 | + actions: { |
| 59 | + getMessages: (c) => c.state.messages, |
| 60 | + |
| 61 | + sendMessage: async (c, userMessage: string) => { |
| 62 | + const userMsg: Message = { |
| 63 | + role: "user", |
| 64 | + content: userMessage, |
| 65 | + timestamp: Date.now(), |
| 66 | + }; |
| 67 | + |
| 68 | + // State changes are automatically persisted |
| 69 | + c.state.messages.push(userMsg); |
| 70 | + |
| 71 | + const { text } = await generateText({ |
| 72 | + model: openai("gpt-4o"), |
| 73 | + prompt: userMessage, |
| 74 | + messages: c.state.messages, |
| 75 | + tools: { |
| 76 | + weather: tool({ |
| 77 | + description: "Get the weather in a location", |
| 78 | + parameters: z.object({ |
| 79 | + location: z |
| 80 | + .string() |
| 81 | + .describe("The location to get the weather for"), |
| 82 | + }), |
| 83 | + execute: async ({ location }) => { |
| 84 | + return await getWeather(location); |
| 85 | + }, |
| 86 | + }), |
| 87 | + }, |
| 88 | + }); |
| 89 | + |
| 90 | + const assistantMsg: Message = { |
| 91 | + role: "assistant", |
| 92 | + content: text, |
| 93 | + timestamp: Date.now(), |
| 94 | + }; |
| 95 | + c.state.messages.push(assistantMsg); |
| 96 | + |
| 97 | + // Send realtime events to all connected clients |
| 98 | + c.broadcast("messageReceived", assistantMsg); |
| 99 | + |
| 100 | + return assistantMsg; |
| 101 | + }, |
| 102 | + }, |
| 103 | +}); |
| 104 | + |
| 105 | +export const registry = setup({ |
| 106 | + use: { aiAgent }, |
| 107 | +}); |
| 108 | +``` |
| 109 | + |
| 110 | +```typescript {{"title":"src/app/components/AgentChat.tsx"}} |
| 111 | +import { createRivetKit } from "@rivetkit/next-js/client"; |
| 112 | +import { useEffect, useState } from "react"; |
| 113 | +import { registry } from "../rivet/registry"; |
| 114 | +import type { Message } from "../backend/types"; |
| 115 | + |
| 116 | +const { useActor } = createRivetKit<typeof registry>(); |
| 117 | + |
| 118 | +export function AgentChat() { |
| 119 | + // Connect to the actor |
| 120 | + const aiAgent = useActor({ |
| 121 | + name: "aiAgent", |
| 122 | + key: ["default"], |
| 123 | + }); |
| 124 | + |
| 125 | + const [messages, setMessages] = useState<Message[]>([]); |
| 126 | + const [input, setInput] = useState(""); |
| 127 | + const [isLoading, setIsLoading] = useState(false); |
| 128 | + |
| 129 | + // Fetch initial messages on load |
| 130 | + useEffect(() => { |
| 131 | + if (aiAgent.connection) { |
| 132 | + aiAgent.connection.getMessages().then(setMessages); |
| 133 | + } |
| 134 | + }, [aiAgent.connection]); |
| 135 | + |
| 136 | + // Subscribe to realtime events |
| 137 | + aiAgent.useEvent("messageReceived", (message: Message) => { |
| 138 | + setMessages((prev) => [...prev, message]); |
| 139 | + setIsLoading(false); |
| 140 | + }); |
| 141 | + |
| 142 | + const handleSendMessage = async () => { |
| 143 | + if (aiAgent.connection && input.trim()) { |
| 144 | + setIsLoading(true); |
| 145 | + |
| 146 | + const userMessage = { role: "user", content: input, timestamp: Date.now() } as Message; |
| 147 | + setMessages((prev) => [...prev, userMessage]); |
| 148 | + |
| 149 | + await aiAgent.connection.sendMessage(input); |
| 150 | + setInput(""); |
| 151 | + } |
| 152 | + }; |
| 153 | + |
| 154 | + return ( |
| 155 | + <div className="ai-chat"> |
| 156 | + <div className="messages"> |
| 157 | + {messages.length === 0 ? ( |
| 158 | + <div className="empty-message"> |
| 159 | + Ask the AI assistant a question to get started |
| 160 | + </div> |
| 161 | + ) : ( |
| 162 | + messages.map((msg, i) => ( |
| 163 | + <div key={i} className={`message ${msg.role}`}> |
| 164 | + <div className="avatar">{msg.role === "user" ? "👤" : "🤖"}</div> |
| 165 | + <div className="content">{msg.content}</div> |
| 166 | + </div> |
| 167 | + )) |
| 168 | + )} |
| 169 | + {isLoading && ( |
| 170 | + <div className="message assistant loading"> |
| 171 | + <div className="avatar">🤖</div> |
| 172 | + <div className="content">Thinking...</div> |
| 173 | + </div> |
| 174 | + )} |
| 175 | + </div> |
| 176 | + |
| 177 | + <div className="input-area"> |
| 178 | + <input |
| 179 | + value={input} |
| 180 | + onChange={(e) => setInput(e.target.value)} |
| 181 | + onKeyPress={(e) => e.key === "Enter" && handleSendMessage()} |
| 182 | + placeholder="Ask the AI assistant..." |
| 183 | + disabled={isLoading} |
| 184 | + /> |
| 185 | + <button |
| 186 | + onClick={handleSendMessage} |
| 187 | + disabled={isLoading || !input.trim()} |
| 188 | + > |
| 189 | + Send |
| 190 | + </button> |
| 191 | + </div> |
| 192 | + </div> |
| 193 | + ); |
| 194 | +} |
| 195 | + |
| 196 | +``` |
| 197 | + |
| 198 | +</CodeGroup> |
| 199 | + |
| 200 | +## Why run Rivet on Vercel? |
| 201 | + |
| 202 | +Running Rivet Actors on Vercel sidesteps several constraints with Cloudflare Durable Objects: |
| 203 | + |
| 204 | +- **Industry standard runtime**: Runs on Node.js today (with Bun coming soon to Vercel Functions). No proprietary runtime, use the Node APIs and NPM packages you already know. |
| 205 | +- **More memory & CPU**: Vercel Functions let you configure memory and vCPU per function (up to 4 GB RAM, 2 vCPU cores). This enables larger PDF processing, multiplayer games, agent context, and heavier compute that would exceed typical isolate caps. |
| 206 | +- **Developer experience**: Deploy actors alongside your Next.js app in one place, leverage Vercel's best-in-class observability, and use Rivet's built-in inspector for deep visibility into your actors. |
| 207 | +- **Regions you choose.** Set regions directly to keep actors close to your primary database or within compliance boundaries. |
| 208 | +- **Portability by design.** Your actor logic is built on RivetKit — an open-source library — and runs on industry standard runtimes. If your needs change, you can move between clouds or self-host without rewriting your application. |
| 209 | + |
| 210 | +## Getting started on Vercel |
| 211 | + |
| 212 | +1. Sign in to [Rivet Cloud](https://dashboard.rivet.dev) or [self-host Rivet](/docs/self-hosting) |
| 213 | +2. Select _Vercel_ |
| 214 | +3. Follow instructions to deploy the [Vercel starter template](https://github.com/rivet-dev/template-vercel) or [integrate Rivet into your existing Next.js application](https://www.rivet.dev/docs/actors/quickstart/next-js/) |
| 215 | + |
| 216 | +## Support |
| 217 | + |
| 218 | +- [GitHub Issues](https://github.com/rivet-dev/rivet/issues) |
| 219 | +- [Discord](https://rivet.dev/discord) |
| 220 | + |
0 commit comments