diff --git a/website/public/solutions/world-map.svg b/website/public/solutions/world-map.svg new file mode 100644 index 0000000000..b4679dce58 --- /dev/null +++ b/website/public/solutions/world-map.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/website/src/app/(v2)/(marketing)/solutions/app-generators/page.tsx b/website/src/app/(v2)/(marketing)/solutions/app-generators/page.tsx new file mode 100644 index 0000000000..c351c4205e --- /dev/null +++ b/website/src/app/(v2)/(marketing)/solutions/app-generators/page.tsx @@ -0,0 +1,603 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + Terminal, + Zap, + Globe, + ArrowRight, + Box, + Database, + Check, + Cpu, + RefreshCw, + Clock, + Cloud, + LayoutGrid, + Activity, + Wifi, + AlertCircle, + Gamepad2, + MessageSquare, + Bot, + Users, + FileText, + Workflow, + Gauge, + Eye, + Brain, + Sparkles, + Network, + HardDrive, + Container, + Coins, + Cpu as Chip, + Wand2, +} from "lucide-react"; +import { motion } from "framer-motion"; + +// --- Shared Design Components --- +const Badge = ({ text, color = "pink" }) => { + const colorClasses = { + orange: "text-orange-400 border-orange-500/20 bg-orange-500/10", + blue: "text-blue-400 border-blue-500/20 bg-blue-500/10", + purple: "text-purple-400 border-purple-500/20 bg-purple-500/10", + pink: "text-pink-400 border-pink-500/20 bg-pink-500/10", + }; + + return ( +
+ + {text} +
+ ); +}; + +const CodeBlock = ({ code, fileName = "platform.ts" }) => { + return ( +
+
+
+
+
+
+
+
+
{fileName}
+
+
+
+					
+						{code.split("\n").map((line, i) => (
+							
+ + {i + 1} + + + {(() => { + // Simple custom tokenizer for this snippet + const tokens = []; + let current = line; + + // Handle comments first (consume rest of line) + const commentIndex = current.indexOf("//"); + let comment = ""; + if (commentIndex !== -1) { + comment = current.slice(commentIndex); + current = current.slice(0, commentIndex); + } + + // Split remaining code by delimiters but keep them + const parts = current.split(/([a-zA-Z0-9_$]+|"[^"]*"|'[^']*'|\s+|[(){},.;:[\]])/g).filter(Boolean); + + parts.forEach((part, j) => { + const trimmed = part.trim(); + + // Keywords + if (["import", "from", "export", "const", "return", "async", "await", "function", "let", "var"].includes(trimmed)) { + tokens.push({part}); + } + // Functions & Special Rivet Terms + else if (["actor", "spawn", "rpc", "loadCode", "deployGeneratedApp"].includes(trimmed)) { + tokens.push({part}); + } + // Object Keys / Properties / Methods + else if (["state", "actions", "runningApps", "code", "schema", "env", "DATABASE_MODE", "url", "status", "appActor"].includes(trimmed)) { + tokens.push({part}); + } + // Strings + else if (part.startsWith('"') || part.startsWith("'")) { + tokens.push({part}); + } + // Numbers + else if (!isNaN(Number(trimmed)) && trimmed !== "") { + tokens.push({part}); + } + // Default (punctuation, variables like 'c', etc) + else { + tokens.push({part}); + } + }); + + if (comment) { + tokens.push({comment}); + } + + return tokens; + })()} + +
+ ))} +
+
+
+
+ ); +}; + +// --- Refined Feature Card matching landing page style with color highlights --- +const SolutionCard = ({ title, description, icon: Icon, color = "pink" }) => { + const getColorClasses = (col) => { + switch (col) { + case "orange": + return { + bg: "bg-orange-500/10", + text: "text-orange-400", + hoverBg: "group-hover:bg-orange-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(249,115,22,0.5)]", + border: "border-orange-500", + glow: "rgba(249,115,22,0.15)", + }; + case "blue": + return { + bg: "bg-blue-500/10", + text: "text-blue-400", + hoverBg: "group-hover:bg-blue-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(59,130,246,0.5)]", + border: "border-blue-500", + glow: "rgba(59,130,246,0.15)", + }; + case "purple": + return { + bg: "bg-purple-500/10", + text: "text-purple-400", + hoverBg: "group-hover:bg-purple-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(168,85,247,0.5)]", + border: "border-purple-500", + glow: "rgba(168,85,247,0.15)", + }; + case "pink": + return { + bg: "bg-pink-500/10", + text: "text-pink-400", + hoverBg: "group-hover:bg-pink-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(236,72,153,0.5)]", + border: "border-pink-500", + glow: "rgba(236,72,153,0.15)", + }; + case "zinc": + return { + bg: "bg-zinc-500/10", + text: "text-zinc-400", + hoverBg: "group-hover:bg-zinc-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(113,113,122,0.5)]", + border: "border-zinc-500", + glow: "rgba(113,113,122,0.15)", + }; + default: + return { + bg: "bg-pink-500/10", + text: "text-pink-400", + hoverBg: "group-hover:bg-pink-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(236,72,153,0.5)]", + border: "border-pink-500", + glow: "rgba(236,72,153,0.15)", + }; + } + }; + const c = getColorClasses(color); + + return ( +
+ {/* Top Shine Highlight */} +
+ + {/* Top Left Reflection/Glow */} +
+ {/* Sharp Edge Highlight (Masked) */} +
+ +
+
+
+
+ +
+

{title}

+
+
+

{description}

+
+
+ ); +}; + +// --- Page Sections --- +const Hero = () => ( +
+
+ +
+
+
+ + + + The Backend for
+ App Generators. +
+ + + Don't burn tokens managing database schemas in your context window. Give every generated app its own isolated, stateful Actor instantly. + + + + +
+
+
+
+ { + // 1. Create a dedicated actor for this app + const appActor = await c.spawn("generated_backend", { + env: { DATABASE_MODE: 'in-memory' } + }); + + // 2. Hot-load the logic + await appActor.rpc.loadCode(code); + + return { url: appActor.url, status: "ready" }; + } + } +});`} + /> +
+
+
+
+
+); + +const TokenEfficiencyVisualizer = () => { + return ( +
+
+
+ + Stop Paying for State + + + Traditional AI coding tools waste thousands of tokens passing database schemas and state history back and forth. Rivet Actors hold state in memory, keeping your context window focused on logic. + +
+ +
+ {/* Traditional Way */} + +

Traditional LLM Backend

+ + {/* Stack Visualization */} +
+ {/* Context Window (Bloated) */} +
+
DB Schema (2k tokens)
+
User History (4k tokens)
+
ORM Definitions (2k)
+
Actual Logic
+ + {/* Overflow fade */} +
+
+ $$$ High Cost / Slow +
+ + + {/* Rivet Way */} + +
+

Rivet Actor Backend

+ + {/* Stack Visualization */} +
+ {/* Context Window (Lean) */} +
+
Pure Logic
+
+ + {/* The Actor State (Offloaded) */} +
+
+ + Actor Memory +
+
+
+
+ State persisted automatically +
+ ⚡ Low Cost / Fast +
+ +
+
+
+ ); +}; + +const PlatformFeatures = () => { + const features = [ + { + title: "Sandbox Isolation", + description: "Every generated app gets its own Actor. Crashes in one user's app never affect the platform.", + icon: Container, + color: "pink", + }, + { + title: "Zero-Config State", + description: "Your users don't need to setup Postgres. `state.count++` is persisted instantly.", + icon: Database, + color: "blue", + }, + { + title: "Token Efficient", + description: "Reduce prompt size by 80%. Don't send the DB schema with every request; just send the logic.", + icon: Coins, + color: "purple", + }, + { + title: "Instant Deploy", + description: "Spawn a new backend in <10ms. Perfect for 'Click to Run' AI interfaces.", + icon: Zap, + color: "orange", + }, + { + title: "Hot Swappable", + description: "Update the actor's behavior in real-time as the AI generates new code versions.", + icon: RefreshCw, + color: "zinc", + }, + { + title: "Streaming Outputs", + description: "Pipe stdout/stderr from the actor directly to your user's browser via WebSockets.", + icon: Activity, + color: "pink", + }, + ]; + + return ( +
+
+ +

Infrastructure for Generation

+

Primitives designed for AI code generation platforms.

+
+ +
+ {features.map((feat, idx) => ( + + + + ))} +
+
+
+ ); +}; + +const UseCases = () => { + const cases = [ + { + title: "No-Code Builders", + desc: "Let users describe an app and deploy a real, stateful backend instantly.", + }, + { + title: "Interactive Tutors", + desc: "Spawn a coding environment for each student where the AI can verify state.", + }, + { + title: "Internal Tool Gens", + desc: "Generate admin panels on the fly that connect to real APIs and persist settings.", + }, + { + title: "Game Engines", + desc: "Allow users to prompt-generate multiplayer game rules that run on authoritative servers.", + }, + ]; + + return ( +
+
+ + Powering the Next Gen + +
+ {cases.map((c, i) => ( + +
+ +
+

{c.title}

+

{c.desc}

+
+ ))} +
+
+
+ ); +}; + +const Ecosystem = () => ( +
+
+ + Works with your stack + +
+ {["Vercel AI SDK", "LangChain", "OpenAI", "Anthropic", "Replit", "Sandpack"].map((tech, i) => ( + + {tech} + + ))} +
+
+
+); + +export default function AppGeneratorsPage() { + return ( +
+
+ + + + + + + {/* CTA Section */} +
+
+ + Build the platform. + + + Give your users the power of stateful backends without the complexity. + + + + + +
+
+
+
+ ); +} + diff --git a/website/src/app/(v2)/(marketing)/solutions/background-jobs/page.tsx b/website/src/app/(v2)/(marketing)/solutions/background-jobs/page.tsx new file mode 100644 index 0000000000..4f1be6e2f7 --- /dev/null +++ b/website/src/app/(v2)/(marketing)/solutions/background-jobs/page.tsx @@ -0,0 +1,679 @@ +"use client"; + +import { useState } from "react"; +import { + Terminal, + Zap, + Globe, + ArrowRight, + Box, + Database, + Layers, + Check, + Cpu, + RefreshCw, + Clock, + Shield, + Cloud, + Activity, + Workflow, + AlertTriangle, + Archive, + Gauge, + Play, + Link as LinkIcon, +} from "lucide-react"; +import { motion } from "framer-motion"; + +// --- Shared Design Components --- +const Badge = ({ text, color = "orange" }) => { + const colorClasses = { + orange: "text-orange-400 border-orange-500/20 bg-orange-500/10", + blue: "text-blue-400 border-blue-500/20 bg-blue-500/10", + red: "text-red-400 border-red-500/20 bg-red-500/10", + zinc: "text-zinc-400 border-zinc-500/20 bg-zinc-500/10", + }; + + return ( +
+ + {text} +
+ ); +}; + +const CodeBlock = ({ code, fileName = "worker.ts" }) => { + return ( +
+
+
+
+
+
+
+
+
{fileName}
+
+
+
+					
+						{code.split("\n").map((line, i) => (
+							
+ + {i + 1} + + + {(() => { + const tokens = []; + let current = line; + + const commentIndex = current.indexOf("//"); + let comment = ""; + if (commentIndex !== -1) { + comment = current.slice(commentIndex); + current = current.slice(0, commentIndex); + } + + const parts = current.split(/([a-zA-Z0-9_$]+|"[^"]*"|'[^']*'|\s+|[(){},.;:[\]])/g).filter(Boolean); + + parts.forEach((part, j) => { + const trimmed = part.trim(); + + if (["import", "from", "export", "const", "return", "async", "await", "try", "catch", "if"].includes(trimmed)) { + tokens.push({part}); + } else if (["actor", "schedule", "sendEmail", "enqueue", "process"].includes(trimmed)) { + tokens.push({part}); + } else if (["state", "actions", "queue", "job", "attempts", "err", "delay", "shift", "push"].includes(trimmed)) { + tokens.push({part}); + } else if (part.startsWith('"') || part.startsWith("'")) { + tokens.push({part}); + } else if (!isNaN(Number(trimmed)) && trimmed !== "") { + tokens.push({part}); + } else { + tokens.push({part}); + } + }); + + if (comment) { + tokens.push({comment}); + } + + return tokens; + })()} + +
+ ))} +
+
+
+
+ ); +}; + +const SolutionCard = ({ title, description, icon: Icon, color = "orange" }) => { + const getColorClasses = (col) => { + switch (col) { + case "orange": + return { + bg: "bg-orange-500/10", + text: "text-orange-400", + hoverBg: "group-hover:bg-orange-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(249,115,22,0.5)]", + border: "border-orange-500", + glow: "rgba(249,115,22,0.15)", + }; + case "blue": + return { + bg: "bg-blue-500/10", + text: "text-blue-400", + hoverBg: "group-hover:bg-blue-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(59,130,246,0.5)]", + border: "border-blue-500", + glow: "rgba(59,130,246,0.15)", + }; + case "red": + return { + bg: "bg-red-500/10", + text: "text-red-400", + hoverBg: "group-hover:bg-red-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(239,68,68,0.5)]", + border: "border-red-500", + glow: "rgba(239,68,68,0.15)", + }; + case "zinc": + return { + bg: "bg-zinc-500/10", + text: "text-zinc-400", + hoverBg: "group-hover:bg-zinc-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(113,113,122,0.5)]", + border: "border-zinc-500", + glow: "rgba(113,113,122,0.15)", + }; + default: + return { + bg: "bg-orange-500/10", + text: "text-orange-400", + hoverBg: "group-hover:bg-orange-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(249,115,22,0.5)]", + border: "border-orange-500", + glow: "rgba(249,115,22,0.15)", + }; + } + }; + const c = getColorClasses(color); + + return ( +
+
+
+
+ +
+
+
+
+ +
+

{title}

+
+
+

{description}

+
+
+ ); +}; + +const Hero = () => ( +
+
+ +
+
+
+ + + + Background Jobs
+ Redefined. +
+ + + Forget Redis, SQS, and worker fleets. Rivet Actors combine the queue and the worker into a single, persistent entity. Schedule tasks, retry failures, and sleep for free. + + + + + +
+ +
+
+
+ { + c.state.queue.push({ ...job, attempts: 0 }); + c.schedule("process", "immediate"); + }, + + process: async (c) => { + const job = c.state.queue.shift(); + if (!job) return; + + try { + await sendEmail(job); + } catch (err) { + job.attempts++; + // Retry with exponential backoff + const delay = Math.pow(2, job.attempts) + "s"; + c.state.queue.push(job); + c.schedule("process", delay); + } + } + } +});`} + /> +
+
+
+
+
+); + +const QueueArchitecture = () => { + return ( +
+
+
+ + The Actor as a Worker + + + No need for a separate worker fleet. Every actor can schedule its own tasks, sleep while waiting, and wake up to retry failures. + +
+ +
+
+ +
+ {/* Input Stream */} +
+
+
Source
+ +
+ {/* Emitting Jobs Animation */} +
+
+
+
+
+ + {/* The Queue Pipe */} +
+
+ + IN-MEMORY QUEUE +
+ + {/* Background Flow Animation */} +
+
+
+ + {/* Jobs Animation Container - Using pure CSS traverse for smoothness */} +
+ {[0, 1, 2, 3, 4].map((i) => ( +
+ +
+
+
+
+ ))} +
+
+ + {/* The Worker (Actor) */} +
+
+ {/* Pulsing Core */} +
+
+ + + Processing + + {/* Job Ingestion Effect */} +
+
+ {/* Connection Beam */} +
+
+
+
+ +
+
+ ); +}; + +const JobFeatures = () => { + const features = [ + { + title: "Cron Schedules", + description: "Built-in cron support. Wake up an actor every hour, day, or month to perform maintenance or reporting.", + icon: Clock, + color: "orange", + }, + { + title: "Exponential Backoff", + description: "Network flaked? API down? Easily implement robust retry logic with increasing delays between attempts.", + icon: RefreshCw, + color: "blue", + }, + { + title: "Dead Letter Queues", + description: "Move failed jobs to a separate list in state for manual inspection after max retries are exhausted.", + icon: Archive, + color: "red", + }, + { + title: "Rate Limiting", + description: "Protect downstream APIs. Use a token bucket in actor memory to strictly limit outgoing request rates.", + icon: Gauge, + color: "zinc", + }, + { + title: "Batching", + description: "Accumulate webhooks or events in memory and flush them to your database in a single bulk insert.", + icon: Layers, + color: "orange", + }, + { + title: "Job Prioritization", + description: "Use multiple lists (high, normal, low) within your actor state to ensure critical tasks run first.", + icon: AlertTriangle, + color: "blue", + }, + ]; + + return ( +
+
+ +

Complete Job Control

+

Primitives for building reliable background systems.

+
+ +
+ {features.map((feat, idx) => ( + + + + ))} +
+
+
+ ); +}; + +const CaseStudy = () => ( +
+
+
+
+ + + Video Transcoding Pipeline + + + Coordinate a complex, multi-step media processing workflow without managing intermediate state in a database. + +
    + {["Step 1: Upload triggers Actor creation", "Step 2: Actor calls external Transcoder API", "Step 3: Actor sleeps until webhook callback received", "Step 4: Notify user via WebSocket"].map((item, i) => ( + +
    + +
    + {item} +
    + ))} +
+
+ +
+
+
+
+
+ +
+
+
Job: 1080p_Render
+
Duration: 4m 12s
+
+
+
Waiting
+
+
+
> Transcode started. Sleeping...
+
(Actor Hibernated to Disk)
+
< Webhook received. Waking up!
+
+
+ +
+
+
+); + +const UseCases = () => { + const cases = [ + { + title: "Email Drip Campaigns", + desc: "Schedule a sequence of emails for a user. Sleep for days between sends. Store user progress in state.", + }, + { + title: "Report Generation", + desc: "Trigger a heavy aggregation job. Poll the database, build the PDF, and email it when done.", + }, + { + title: "AI Batch Processing", + desc: "Queue thousands of prompts for LLM processing. Rate limit requests to avoid API bans.", + }, + { + title: "Webhook Ingestion", + desc: "Buffer high-velocity webhooks (e.g. Stripe events) in memory and process them reliably in order.", + }, + ]; + + return ( +
+
+ + Built for Reliability + +
+ {cases.map((c, i) => ( + +
+ +
+

{c.title}

+

{c.desc}

+
+ ))} +
+
+
+ ); +}; + +const Ecosystem = () => ( +
+
+ + Works with your stack + +
+ {["Stripe", "Resend", "Twilio", "Slack", "OpenAI", "Postgres"].map((tech, i) => ( + + {tech} + + ))} +
+
+
+); + +export default function BackgroundJobsPage() { + return ( +
+
+ + + + + + + + {/* CTA Section */} +
+
+ + Fire and forget. + + + Start building reliable, self-healing background jobs with Rivet. + + + + + +
+
+
+
+ ); +} + diff --git a/website/src/app/(v2)/(marketing)/solutions/user-session-store/page.tsx b/website/src/app/(v2)/(marketing)/solutions/commerce/page.tsx similarity index 55% rename from website/src/app/(v2)/(marketing)/solutions/user-session-store/page.tsx rename to website/src/app/(v2)/(marketing)/solutions/commerce/page.tsx index bb06e9a0ee..cc12e09196 100644 --- a/website/src/app/(v2)/(marketing)/solutions/user-session-store/page.tsx +++ b/website/src/app/(v2)/(marketing)/solutions/commerce/page.tsx @@ -1,49 +1,24 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState } from "react"; import { Terminal, Zap, Globe, ArrowRight, - Box, Database, Check, - Cpu, RefreshCw, Clock, - Cloud, - LayoutGrid, - Activity, - Wifi, - AlertCircle, - Gamepad2, - MessageSquare, - Bot, + Lock, + ShoppingCart, + ShoppingBag, + Search, + Lightbulb, Users, - FileText, - Workflow, - Gauge, - Eye, - Brain, - Sparkles, - Network, - Calendar, - GitBranch, - Timer, - Mail, CreditCard, - Bell, - Server, - Sword, - Trophy, - Target, - Fingerprint, - Cookie, - Lock, Smartphone, - ShoppingCart, - Shield, + Link as LinkIcon, } from "lucide-react"; import { motion } from "framer-motion"; @@ -52,9 +27,9 @@ const Badge = ({ text, color = "cyan" }) => { const colorClasses = { orange: "text-orange-400 border-orange-500/20 bg-orange-500/10", blue: "text-blue-400 border-blue-500/20 bg-blue-500/10", - purple: "text-purple-400 border-purple-500/20 bg-purple-500/10", - emerald: "text-emerald-400 border-emerald-500/20 bg-emerald-500/10", red: "text-red-400 border-red-500/20 bg-red-500/10", + pink: "text-pink-400 border-pink-500/20 bg-pink-500/10", + indigo: "text-indigo-400 border-indigo-500/20 bg-indigo-500/10", cyan: "text-cyan-400 border-cyan-500/20 bg-cyan-500/10", }; @@ -62,13 +37,13 @@ const Badge = ({ text, color = "cyan" }) => {
- + {text}
); }; -const CodeBlock = ({ code, fileName = "session.ts" }) => { +const CodeBlock = ({ code, fileName = "cart.ts" }) => { return (
@@ -80,9 +55,9 @@ const CodeBlock = ({ code, fileName = "session.ts" }) => {
{fileName}
-
-
-					
+			
+
+					
 						{code.split("\n").map((line, i) => (
 							
@@ -90,11 +65,9 @@ const CodeBlock = ({ code, fileName = "session.ts" }) => { {(() => { - // Simple custom tokenizer for this snippet const tokens = []; let current = line; - // Handle comments first (consume rest of line) const commentIndex = current.indexOf("//"); let comment = ""; if (commentIndex !== -1) { @@ -102,34 +75,22 @@ const CodeBlock = ({ code, fileName = "session.ts" }) => { current = current.slice(0, commentIndex); } - // Split remaining code by delimiters but keep them const parts = current.split(/([a-zA-Z0-9_$]+|"[^"]*"|'[^']*'|\s+|[(){},.;:[\]])/g).filter(Boolean); parts.forEach((part, j) => { const trimmed = part.trim(); - // Keywords - if (["import", "from", "export", "const", "return", "async", "await", "function", "let", "var", "if", "else"].includes(trimmed)) { + if (["import", "from", "export", "const", "return", "async", "await", "function", "if", "throw"].includes(trimmed)) { tokens.push({part}); - } - // Functions & Special Rivet Terms - else if (["actor", "broadcast", "spawn", "rpc", "schedule", "token", "generate"].includes(trimmed)) { + } else if (["actor", "recommend", "addItem", "rpc", "inventory", "reserve", "ai", "broadcast"].includes(trimmed)) { tokens.push({part}); - } - // Object Keys / Properties / Methods - else if (["state", "actions", "user", "cart", "preferences", "theme", "login", "userData", "addToCart", "item", "push", "destroy"].includes(trimmed)) { + } else if (["state", "actions", "items", "history", "productId", "qty", "stock", "recent", "slice"].includes(trimmed)) { tokens.push({part}); - } - // Strings - else if (part.startsWith('"') || part.startsWith("'")) { + } else if (part.startsWith('"') || part.startsWith("'")) { tokens.push({part}); - } - // Numbers - else if (!isNaN(Number(trimmed)) && trimmed !== "") { + } else if (!isNaN(Number(trimmed)) && trimmed !== "") { tokens.push({part}); - } - // Default (punctuation, variables like 'c', etc) - else { + } else { tokens.push({part}); } }); @@ -150,7 +111,6 @@ const CodeBlock = ({ code, fileName = "session.ts" }) => { ); }; -// --- Refined Session Card matching landing page style with color highlights --- const SolutionCard = ({ title, description, icon: Icon, color = "cyan" }) => { const getColorClasses = (col) => { switch (col) { @@ -172,24 +132,6 @@ const SolutionCard = ({ title, description, icon: Icon, color = "cyan" }) => { border: "border-blue-500", glow: "rgba(59,130,246,0.15)", }; - case "purple": - return { - bg: "bg-purple-500/10", - text: "text-purple-400", - hoverBg: "group-hover:bg-purple-500/20", - hoverShadow: "group-hover:shadow-[0_0_15px_rgba(168,85,247,0.5)]", - border: "border-purple-500", - glow: "rgba(168,85,247,0.15)", - }; - case "emerald": - return { - bg: "bg-emerald-500/10", - text: "text-emerald-400", - hoverBg: "group-hover:bg-emerald-500/20", - hoverShadow: "group-hover:shadow-[0_0_15px_rgba(16,185,129,0.5)]", - border: "border-emerald-500", - glow: "rgba(16,185,129,0.15)", - }; case "red": return { bg: "bg-red-500/10", @@ -199,6 +141,24 @@ const SolutionCard = ({ title, description, icon: Icon, color = "cyan" }) => { border: "border-red-500", glow: "rgba(239,68,68,0.15)", }; + case "pink": + return { + bg: "bg-pink-500/10", + text: "text-pink-400", + hoverBg: "group-hover:bg-pink-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(236,72,153,0.5)]", + border: "border-pink-500", + glow: "rgba(236,72,153,0.15)", + }; + case "indigo": + return { + bg: "bg-indigo-500/10", + text: "text-indigo-400", + hoverBg: "group-hover:bg-indigo-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(99,102,241,0.5)]", + border: "border-indigo-500", + glow: "rgba(99,102,241,0.15)", + }; case "cyan": return { bg: "bg-cyan-500/10", @@ -223,17 +183,13 @@ const SolutionCard = ({ title, description, icon: Icon, color = "cyan" }) => { return (
- {/* Top Shine Highlight */}
- - {/* Top Left Reflection/Glow */}
- {/* Sharp Edge Highlight (Masked) */}
@@ -251,7 +207,6 @@ const SolutionCard = ({ title, description, icon: Icon, color = "cyan" }) => { ); }; -// --- Page Sections --- const Hero = () => (
@@ -259,7 +214,7 @@ const Hero = () => (
- + ( transition={{ duration: 0.5 }} className="text-5xl md:text-7xl font-medium text-white tracking-tight leading-[1.1] mb-6" > - Session State.
- Instantly Available. + The Cart that
+ Never Forgets.
( transition={{ duration: 0.5, delay: 0.1 }} className="text-lg md:text-xl text-zinc-400 leading-relaxed mb-8 max-w-lg" > - Replace Redis and cookies with persistent Actors. Attach rich, real-time state to every user, instantly accessible from the edge without database latency. + Eliminate database contention on launch day. Use Rivet Actors to hold shopping carts, reserve inventory, and power agentic search sessions across devices. + ( className="flex flex-col sm:flex-row items-center gap-4" >
+
{ - c.state.user = userData; - // Set expiry for inactivity (30 mins) - c.schedule("destroy", "30m"); - return c.token.generate(); + // Instant recommendations based on hot state + recommend: async (c) => { + const recent = c.state.history.slice(-5); + return await c.ai.recommend(recent); }, - addToCart: (c, item) => { - c.state.cart.push(item); - // Broadcast update to all user's tabs - c.broadcast("cart_updated", c.state.cart); + addItem: async (c, { productId, qty }) => { + const stock = await c.rpc.inventory.reserve(productId, qty); + if (!stock) throw "Out of Stock"; + + c.state.items.push({ productId, qty }); + c.broadcast("cart_updated", c.state); } } });`} @@ -328,20 +282,11 @@ export const userSession = actor({
); -const LatencyArchitecture = () => { - const [step, setStep] = useState(0); - - useEffect(() => { - const interval = setInterval(() => { - setStep((s) => (s + 1) % 4); - }, 1500); - return () => clearInterval(interval); - }, []); - +const CommerceArchitecture = () => { return (
-
+
{ transition={{ duration: 0.5 }} className="text-3xl md:text-5xl font-medium text-white mb-6 tracking-tight" > - Zero-Latency Profile Access + The Universal Session - Traditional architectures require a database round-trip for every request to fetch user sessions. Rivet Actors keep the user's state hot in memory at the edge. + Stop syncing database rows to keep devices in sync. With Rivet, the Actor is the session. All devices connect to the same living process.
-
- {/* Visualization */} -
-
- {/* User */} -
-
- -
- Request -
+
+
- {/* Path */} -
- {/* Packet */} -
-
-
+
+ {/* Left: Mobile User */} +
+
+
+ +
Mobile App
- {/* The Session Actor */} -
-
- +
+ +1 Sneakers
- Session Actor - {step === 2 && ( -
- Memory Hit -
- )}
- {/* Comparison text */} -
-
-
- Postgres: 45ms -
-
-
- Rivet: 2ms + {/* Middle: The Session Actor */} +
+
+
+ +
Cart Actor
+
1 Item • $120
-
- {/* Feature List */} -
- -

- - Instant Read/Write -

-

- State is stored in RAM. Reading user preferences or checking permissions happens in microseconds, not milliseconds. -

-
-
- -

- - Edge Routing -

-

- Actors are automatically instantiated in the region closest to the user, ensuring global low-latency access. -

-
-
- -

- - Secure by Default -

-

- Isolated memory spaces for every user. No risk of cross-tenant data leaks or cache poisoning. -

-
+ {/* Right: Desktop User */} +
+
+
+
+ +
Web Store
+ +
+ Synced +
+
+
@@ -464,69 +354,59 @@ const LatencyArchitecture = () => { ); }; -const SessionFeatures = () => { +const CommerceFeatures = () => { const features = [ { - title: "Live Sync", - description: "Changes to session data (e.g. dark mode toggle) are broadcast instantly to all open tabs or devices via WebSockets.", - icon: Wifi, + title: "Inventory Reservation", + description: "Hold items in the actor's memory for 10 minutes. If the user doesn't checkout, release stock instantly.", + icon: Lock, color: "cyan", }, { - title: "TTL & Expiry", - description: "Automatically destroy session actors after a period of inactivity to free up resources and enforce security.", - icon: Clock, - color: "orange", + title: "Universal Cart", + description: "The same cart state follows the user from mobile to desktop to tablet. No refresh required.", + icon: ShoppingBag, + color: "blue", }, { - title: "Cart Persistence", - description: "Never lose a shopping cart item again. State survives server restarts and follows the user across devices.", - icon: ShoppingCart, - color: "emerald", + title: "Agentic Search", + description: "Use the actor's session history to feed a vector search instantly, re-ranking results based on user intent.", + icon: Search, + color: "indigo", }, { - title: "Presence", - description: "Know exactly when a user is online. The Actor exists only while the user is connected (or for a grace period).", - icon: Activity, - color: "blue", + title: "Live Recommendations", + description: "Update suggested products in real-time as the user scrolls, based on their immediate view history held in RAM.", + icon: Lightbulb, + color: "orange", }, { - title: "Auth Tokens", - description: "Generate and validate JWTs directly within the Actor. Revoke tokens instantly by killing the Actor.", - icon: Shield, + title: "Flash Sales", + description: "Handle huge spikes in traffic. 100k users can add to cart simultaneously without locking your database.", + icon: Zap, color: "red", }, { - title: "Device Handover", - description: "Start a task on mobile, finish on desktop. The shared Actor state creates a seamless continuity.", - icon: Smartphone, - color: "purple", + title: "Collaborative Shopping", + description: "Allow multiple users to view and edit the same cart (e.g. families or procurement teams).", + icon: Users, + color: "pink", }, ]; return (
-
- - More than just a Cookie - - - Turn your passive session store into an active engine for user experience. - -
+ +

Built for Conversion

+

Speed is revenue. Rivet makes your store feel instant.

+
{features.map((feat, idx) => ( @@ -546,7 +426,7 @@ const SessionFeatures = () => { ); }; -const UseCases = () => ( +const CaseStudy = () => (
@@ -559,7 +439,7 @@ const UseCases = () => ( transition={{ duration: 0.5 }} className="text-3xl md:text-5xl font-medium text-white mb-6 tracking-tight" > - Real-time E-Commerce + High-Volume Drop ( transition={{ duration: 0.5, delay: 0.1 }} className="text-lg text-zinc-400 mb-8 leading-relaxed" > - A high-volume storefront where cart inventory is reserved instantly and stock levels update live. + A streetwear brand launching a limited edition collection. 50,000 users arrive in 60 seconds. - - {[ - "Inventory Locks: Items reserved in Actor memory", - "Dynamic Pricing: Personalized discounts applied instantly", - "Cross-Device: Cart updates on phone appear on laptop", - ].map((item, i) => ( -
  • +
      + {["No Overselling: Atomic decrement of inventory in memory", "Queueing: Fair FIFO processing of checkout requests", "Instant Feedback: UI updates immediately on success/fail"].map((item, i) => ( +
      {item} - +
      ))} - +
  • (
    - +
    -
    User: alex_92
    -
    Session Active (2 devices)
    +
    Item: Retro High '85
    +
    Stock: 42 / 5000
    -
    Online
    +
    Selling Fast
    - Item Added: Mechanical Keyboard - +$149.00 + User: guest_9921 + Checkout Success
    -
    - Broadcast: "cart_update" -> 2 clients +
    + User: guest_4402 + Cart Expired
    -
    -
    -
    -
    +
    +
    +
    Inventory Load: 98%
    @@ -633,6 +510,61 @@ const UseCases = () => (
    ); +const UseCases = () => { + const cases = [ + { + title: "Marketplaces", + desc: "Coordinate buyers and sellers in real-time auctions with instant bid updates.", + }, + { + title: "Ticketing", + desc: "Reserve seats for concerts or events. Prevent double-booking with atomic locks.", + }, + { + title: "Food Delivery", + desc: "Track order status, driver location, and inventory in a single stateful entity.", + }, + { + title: "Subscription Apps", + desc: "Manage access rights and feature gating dynamically based on payment status.", + }, + ]; + + return ( +
    +
    + + Commerce at Scale + +
    + {cases.map((c, i) => ( + +
    + +
    +

    {c.title}

    +

    {c.desc}

    +
    + ))} +
    +
    +
    + ); +}; + const Ecosystem = () => (
    @@ -643,10 +575,10 @@ const Ecosystem = () => ( transition={{ duration: 0.5 }} className="text-3xl md:text-5xl font-medium text-white mb-12 tracking-tight" > - Works with your auth provider + Works with your stack
    - {["Clerk", "Auth0", "Supabase Auth", "NextAuth.js", "Stytch", "Firebase"].map((tech, i) => ( + {["Shopify", "Stripe", "Medusa", "BigCommerce", "Adyen", "Algolia"].map((tech, i) => ( (
    ); -export default function UserSessionStorePage() { +export default function CommercePage() { return (
    - - + + + @@ -683,7 +616,7 @@ export default function UserSessionStorePage() { transition={{ duration: 0.5 }} className="text-4xl md:text-5xl font-medium text-white mb-6 tracking-tight" > - Stop querying the database. + Sell without limits. - Move your user state to the edge with Rivet Actors. + Build shopping experiences that are fast, reliable, and instantly synchronized.
    diff --git a/website/src/app/(v2)/(marketing)/solutions/geo-distributed-db/page.tsx b/website/src/app/(v2)/(marketing)/solutions/geo-distributed-db/page.tsx new file mode 100644 index 0000000000..6f7206c329 --- /dev/null +++ b/website/src/app/(v2)/(marketing)/solutions/geo-distributed-db/page.tsx @@ -0,0 +1,995 @@ +"use client"; + +import { useState } from "react"; +import { + Terminal, + Zap, + Globe, + ArrowRight, + Database, + Layers, + Check, + Cpu, + RefreshCw, + Clock, + Shield, + Cloud, + Activity, + Server, + Map, + GitBranch, + Landmark, +} from "lucide-react"; +import { motion } from "framer-motion"; + +// --- Shared Design Components --- +const Badge = ({ text, color = "blue" }) => { + const colorClasses = { + orange: "text-orange-400 border-orange-500/20 bg-orange-500/10", + blue: "text-blue-400 border-blue-500/20 bg-blue-500/10", + red: "text-red-400 border-red-500/20 bg-red-500/10", + zinc: "text-zinc-400 border-zinc-500/20 bg-zinc-500/10", + purple: "text-purple-400 border-purple-500/20 bg-purple-500/10", + }; + + return ( +
    + + {text} +
    + ); +}; + +const CodeBlock = ({ code, fileName = "global.ts" }) => { + return ( +
    +
    +
    +
    +
    +
    +
    +
    +
    {fileName}
    +
    +
    +
    +					
    +						{code.split("\n").map((line, i) => (
    +							
    + + {i + 1} + + + {(() => { + const tokens = []; + let current = line; + + const commentIndex = current.indexOf("//"); + let comment = ""; + if (commentIndex !== -1) { + comment = current.slice(commentIndex); + current = current.slice(0, commentIndex); + } + + const parts = current.split(/([a-zA-Z0-9_$]+|"[^"]*"|'[^']*'|\s+|[(){},.;:[\]])/g).filter(Boolean); + + parts.forEach((part, j) => { + const trimmed = part.trim(); + + if (["import", "from", "export", "const", "return", "async", "await", "function"].includes(trimmed)) { + tokens.push({part}); + } else if (["actor", "broadcast", "getItem", "updateStock"].includes(trimmed)) { + tokens.push({part}); + } else if (["state", "actions", "inventory", "region", "id", "qty"].includes(trimmed)) { + tokens.push({part}); + } else if (part.startsWith('"') || part.startsWith("'")) { + tokens.push({part}); + } else if (!isNaN(Number(trimmed)) && trimmed !== "") { + tokens.push({part}); + } else { + tokens.push({part}); + } + }); + + if (comment) { + tokens.push({comment}); + } + + return tokens; + })()} + +
    + ))} +
    +
    +
    +
    + ); +}; + +const SolutionCard = ({ title, description, icon: Icon, color = "blue" }) => { + const getColorClasses = (col) => { + switch (col) { + case "orange": + return { + bg: "bg-orange-500/10", + text: "text-orange-400", + hoverBg: "group-hover:bg-orange-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(249,115,22,0.5)]", + border: "border-orange-500", + glow: "rgba(249,115,22,0.15)", + }; + case "blue": + return { + bg: "bg-blue-500/10", + text: "text-blue-400", + hoverBg: "group-hover:bg-blue-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(59,130,246,0.5)]", + border: "border-blue-500", + glow: "rgba(59,130,246,0.15)", + }; + case "red": + return { + bg: "bg-red-500/10", + text: "text-red-400", + hoverBg: "group-hover:bg-red-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(239,68,68,0.5)]", + border: "border-red-500", + glow: "rgba(239,68,68,0.15)", + }; + case "zinc": + return { + bg: "bg-zinc-500/10", + text: "text-zinc-400", + hoverBg: "group-hover:bg-zinc-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(113,113,122,0.5)]", + border: "border-zinc-500", + glow: "rgba(113,113,122,0.15)", + }; + case "purple": + return { + bg: "bg-purple-500/10", + text: "text-purple-400", + hoverBg: "group-hover:bg-purple-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(168,85,247,0.5)]", + border: "border-purple-500", + glow: "rgba(168,85,247,0.15)", + }; + default: + return { + bg: "bg-blue-500/10", + text: "text-blue-400", + hoverBg: "group-hover:bg-blue-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(59,130,246,0.5)]", + border: "border-blue-500", + glow: "rgba(59,130,246,0.15)", + }; + } + }; + const c = getColorClasses(color); + + return ( +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +

    {title}

    +
    +
    +

    {description}

    +
    +
    + ); +}; + +const Hero = () => ( +
    +
    + +
    +
    +
    + + + + Databases that
    + Span the Globe. +
    + + + Stop wrestling with read replicas and eventual consistency. Rivet Actors replicate state automatically across regions, giving every user local latency for both reads and writes. + + + + + +
    + +
    +
    +
    + c.state.inventory[id], + + // Writes are coordinated globally (Paxos/Raft) + updateStock: (c, id, qty) => { + c.state.inventory[id] = qty; + c.broadcast("stock_update", { id, qty }); + } + } +});`} + /> +
    +
    +
    +
    +
    +); + +const GlobalArchitecture = () => { + return ( +
    +
    +
    + + Latency is the Enemy + + + Don't send your users across the ocean for a database query. Rivet brings the data to the edge, synchronizing state changes across regions. + +
    + +
    + {/* World Map Background - static only */} + World Map + + {/* Animated Activity Dots - full brightness overlay */} + + {/* Eastern Canada - on actual map dots */} + + + + + + + + + + + + + {/* Northeastern Canada */} + + + + + + + + + + + + + {/* Caribbean - Blue dots */} + + + + + {/* Europe - Blue dots */} + + + + + + + + + + + + {/* Western North America - Blue dots */} + + + + + + + + {/* Asia - Blue dots */} + + + + + + + + + + {/* Australia - Blue dots */} + + + + + + + + {/* Africa - Blue dots */} + + + + + + + + {/* South America - Blue dots */} + + + + + + + + + + + + {/* More Europe coverage */} + + + + + + + {/* More Asia coverage - expanded */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* More South America */} + + + + + + + + + + + + {/* More Africa coverage */} + + + + + + + + {/* More Australia coverage */} + + + + + + + + {/* Central North America */} + + + + + + {/* Eastern North America - expanded */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Additional West North America */} + + + + + + + + + + + + + + + + + + + + {/* Additional Africa */} + + + + + + + + + + + + + + + + + + {/* Additional Asia */} + + + + + + + + + + + + + + + + + + + {/* Additional Latin America */} + + + + + + + + + + + + + + + + + + + + {/* Additional Europe */} + + + + + + + + + + + + + + + + + + + + {/* Additional Australia */} + + + + + + + + + + + + + + + + + {/* Alaska */} + + + + + + + + + + + + + + + + + + + + {/* Western Canada */} + + + + + + + + + + + + + + + + + + + + {/* Western USA */} + + + + + + + + + + + + + + + + + + + + {/* Northern South America */} + + + + + + + + + + + + + + + + + + + +
    +
    +
    + ); +}; + +const ReplicationFeatures = () => { + const features = [ + { + title: "Read from Anywhere", + description: "Requests are automatically routed to the nearest regional replica. Reads are served from local memory in <5ms.", + icon: Globe, + color: "blue", + }, + { + title: "Write Locally", + description: "Writes are accepted by the local node and asynchronously replicated to other regions. No global locking required for most ops.", + icon: Zap, + color: "orange", + }, + { + title: "Conflict Resolution", + description: "Built-in CRDT (Conflict-free Replicated Data Types) support handles concurrent edits gracefully.", + icon: GitBranch, + color: "purple", + }, + { + title: "Edge Caching", + description: "Actors act as intelligent caches that can execute logic. Invalidate cache globally with a single broadcast.", + icon: RefreshCw, + color: "zinc", + }, + { + title: "Data Sovereignty", + description: "Pin specific actors to specific regions. Ensure data never leaves a jurisdiction (e.g. Germany) to satisfy compliance.", + icon: Landmark, + color: "red", + }, + { + title: "Partition Tolerance", + description: "If a region goes dark, the rest of the cluster continues to operate. State heals automatically when connectivity returns.", + icon: Activity, + color: "blue", + }, + ]; + + return ( +
    +
    + +

    Distributed Superpowers

    +

    The benefits of a global CDN, but for your application state.

    +
    + +
    + {features.map((feat, idx) => ( + + + + ))} +
    +
    +
    + ); +}; + +const CaseStudy = () => ( +
    +
    +
    +
    + + + Global Inventory System + + + A retail platform tracking stock across warehouses in 12 countries. + +
      + {["Real-time: Stock levels update globally in <100ms", "Resilient: Local warehouses can continue operating if the transatlantic cable creates latency", "Consistent: Overselling prevented via regional allocation pools"].map((item, i) => ( + +
      + +
      + {item} +
      + ))} +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    Item: SKU-992
    +
    Global Stock: 14,200
    +
    +
    +
    Synced
    +
    +
    +
    + New York + 4,200 +
    +
    + London + 3,800 +
    +
    + Tokyo + 6,200 +
    +
    Replication Lag: 45ms
    +
    +
    + +
    +
    +
    +); + +const UseCases = () => { + const cases = [ + { + title: "Global User Profiles", + desc: "Store user settings and session data close to where they are. Login in Tokyo feels as fast as login in NY.", + }, + { + title: "Content Delivery", + desc: "Serve dynamic, personalized content from the edge without hitting a central origin database.", + }, + { + title: "IoT Data Aggregation", + desc: "Ingest sensor data locally in each region, aggregate it, and replicate summaries to HQ.", + }, + { + title: "Multi-Region Failover", + desc: "Keep a hot standby of your entire application state in a second region for instant disaster recovery.", + }, + ]; + + return ( +
    +
    + + Built for Scale + +
    + {cases.map((c, i) => ( + +
    + +
    +

    {c.title}

    +

    {c.desc}

    +
    + ))} +
    +
    +
    + ); +}; + +const Ecosystem = () => ( +
    +
    + + Works with your stack + +
    + {["AWS (Global)", "GCP", "Azure", "Fly.io", "Cloudflare", "Vercel Edge"].map((tech, i) => ( + + {tech} + + ))} +
    +
    +
    +); + +export default function GeoDistributedDBPage() { + return ( +
    +
    + + + + + + + + {/* CTA Section */} +
    +
    + + Go Global. + + + Stop worrying about latency. Start building applications that feel local to everyone, everywhere. + + + + + +
    +
    +
    +
    + ); +} + diff --git a/website/src/app/(v2)/(marketing)/solutions/mcp/page.tsx b/website/src/app/(v2)/(marketing)/solutions/mcp/page.tsx new file mode 100644 index 0000000000..6e80c32908 --- /dev/null +++ b/website/src/app/(v2)/(marketing)/solutions/mcp/page.tsx @@ -0,0 +1,639 @@ +"use client"; + +import { useState } from "react"; +import { + Terminal, + Zap, + Globe, + ArrowRight, + Database, + Check, + Cpu, + RefreshCw, + Clock, + Shield, + Cloud, + Activity, + Wifi, + Lock, + Network, + HardDrive, + Users, + Sparkles, + Plug, + Link as LinkIcon, +} from "lucide-react"; +import { motion } from "framer-motion"; + +// --- Shared Design Components --- +const Badge = ({ text, color = "indigo" }) => { + const colorClasses = { + orange: "text-orange-400 border-orange-500/20 bg-orange-500/10", + blue: "text-blue-400 border-blue-500/20 bg-blue-500/10", + red: "text-red-400 border-red-500/20 bg-red-500/10", + zinc: "text-zinc-400 border-zinc-500/20 bg-zinc-500/10", + indigo: "text-indigo-400 border-indigo-500/20 bg-indigo-500/10", + }; + + return ( +
    + + {text} +
    + ); +}; + +const CodeBlock = ({ code, fileName = "mcp_server.ts" }) => { + return ( +
    +
    +
    +
    +
    +
    +
    +
    +
    {fileName}
    +
    +
    +
    +					
    +						{code.split("\n").map((line, i) => (
    +							
    + + {i + 1} + + + {(() => { + const tokens = []; + let current = line; + + const commentIndex = current.indexOf("//"); + let comment = ""; + if (commentIndex !== -1) { + comment = current.slice(commentIndex); + current = current.slice(0, commentIndex); + } + + const parts = current.split(/([a-zA-Z0-9_$]+|"[^"]*"|'[^']*'|\s+|[(){},.;:[\]])/g).filter(Boolean); + + parts.forEach((part, j) => { + const trimmed = part.trim(); + + if (["import", "from", "export", "const", "return", "async", "await", "function", "let", "var"].includes(trimmed)) { + tokens.push({part}); + } else if (["actor", "McpServer", "tool", "connect", "z"].includes(trimmed)) { + tokens.push({part}); + } else if (["state", "actions", "preferences", "history", "name", "version", "key", "val", "content", "type", "text", "server", "transport"].includes(trimmed)) { + tokens.push({part}); + } else if (part.startsWith('"') || part.startsWith("'")) { + tokens.push({part}); + } else if (!isNaN(Number(trimmed)) && trimmed !== "") { + tokens.push({part}); + } else { + tokens.push({part}); + } + }); + + if (comment) { + tokens.push({comment}); + } + + return tokens; + })()} + +
    + ))} +
    +
    +
    +
    + ); +}; + +const SolutionCard = ({ title, description, icon: Icon, color = "indigo" }) => { + const getColorClasses = (col) => { + switch (col) { + case "orange": + return { + bg: "bg-orange-500/10", + text: "text-orange-400", + hoverBg: "group-hover:bg-orange-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(249,115,22,0.5)]", + border: "border-orange-500", + glow: "rgba(249,115,22,0.15)", + }; + case "blue": + return { + bg: "bg-blue-500/10", + text: "text-blue-400", + hoverBg: "group-hover:bg-blue-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(59,130,246,0.5)]", + border: "border-blue-500", + glow: "rgba(59,130,246,0.15)", + }; + case "red": + return { + bg: "bg-red-500/10", + text: "text-red-400", + hoverBg: "group-hover:bg-red-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(239,68,68,0.5)]", + border: "border-red-500", + glow: "rgba(239,68,68,0.15)", + }; + case "zinc": + return { + bg: "bg-zinc-500/10", + text: "text-zinc-400", + hoverBg: "group-hover:bg-zinc-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(113,113,122,0.5)]", + border: "border-zinc-500", + glow: "rgba(113,113,122,0.15)", + }; + case "indigo": + return { + bg: "bg-indigo-500/10", + text: "text-indigo-400", + hoverBg: "group-hover:bg-indigo-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(99,102,241,0.5)]", + border: "border-indigo-500", + glow: "rgba(99,102,241,0.15)", + }; + default: + return { + bg: "bg-indigo-500/10", + text: "text-indigo-400", + hoverBg: "group-hover:bg-indigo-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(99,102,241,0.5)]", + border: "border-indigo-500", + glow: "rgba(99,102,241,0.15)", + }; + } + }; + const c = getColorClasses(color); + + return ( +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +

    {title}

    +
    +
    +

    {description}

    +
    +
    + ); +}; + +const Hero = () => ( +
    +
    + +
    +
    +
    + + + + The Stateful
    + MCP Server. +
    + + + Don't just connect LLMs to tools. Connect them to state. Rivet Actors let you deploy persistent, user-aware MCP servers that remember everything. + + + + +
    +
    +
    +
    + { + const server = new McpServer({ + name: "PersonalContext", + version: "1.0.0" + }); + + // Tool that reads/writes persistent state + server.tool("remember", { key: z.string(), val: z.string() }, + async ({ key, val }) => { + c.state.preferences[key] = val; + return { content: [{ type: "text", text: "Saved." }] }; + } + ); + + return server.connect(c.transport); + } + } +});`} + /> +
    +
    +
    +
    +
    +); + +const ProtocolArchitecture = () => { + return ( +
    +
    +
    + + Stateful vs. Stateless MCP + + + Standard MCP servers are often stateless processes. Rivet Actors give each connection its own long-lived memory, enabling personalized and continuous AI interactions. + +
    + +
    +
    + +
    + {/* Left: Client (Claude/Cursor) */} +
    +
    +
    Client
    + +
    + Claude / Cursor +
    + + {/* Middle: The Protocol Pipe */} +
    +
    +
    + JSON-RPC + SSE +
    +
    + + {/* Right: Rivet Actor */} +
    +
    +
    + +
    +
    +
    +
    +
    +
    + Stateful Actor +
    +
    +
    +
    +
    + ); +}; + +const MCPFeatures = () => { + const features = [ + { + title: "User-Specific Servers", + description: "Spawn a unique MCP server for every user. Give them their own memory, preferences, and authentication context.", + icon: Users, + color: "indigo", + }, + { + title: "Persistent Memory", + description: "Tools can write to `c.state`. Data survives server restarts and is instantly available next time the user connects.", + icon: HardDrive, + color: "blue", + }, + { + title: "Long-Running Tools", + description: "Execute tools that take minutes or hours (e.g. scraping, compiling). The Actor stays alive while the LLM waits.", + icon: Clock, + color: "orange", + }, + { + title: "SSE & Stdio Support", + description: "Connect via HTTP Server-Sent Events for web agents, or Stdio for local desktop apps like Claude.", + icon: Plug, + color: "zinc", + }, + { + title: "Secure Headers", + description: "Pass authentication tokens from the client directly to the Actor. Secure your tools with per-user permissions.", + icon: Lock, + color: "red", + }, + { + title: "Instant Wake", + description: "Your MCP server hibernates when unused (0 cost) and wakes up in milliseconds when the LLM calls a tool.", + icon: Zap, + color: "indigo", + }, + ]; + + return ( +
    +
    + +

    Protocol Superpowers

    +

    Enhance the Model Context Protocol with durable compute.

    +
    + +
    + {features.map((feat, idx) => ( + + + + ))} +
    +
    +
    + ); +}; + +const CaseStudy = () => ( +
    +
    +
    +
    + + + Persistent Cloud IDE + + + Deploy a dedicated MCP server for each developer that maintains project context, terminal history, and language server state across sessions. + +
      + {[ + "Hot-Swappable: Update tools on the fly without restarting context", + "Session Recall: 'Remember that bug from Tuesday?' actually works", + "Secure Tunnel: Authenticated connection between local editor and cloud actor", + ].map((item, i) => ( + +
      + +
      + {item} +
      + ))} +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    DevSession #8492
    +
    Status: Listening
    +
    +
    +
    Connected
    +
    +
    +
    > User: Refactor auth.ts based on yesterday's logs.
    +
    < MCP: Retrieving logs from Actor state... Context found. Refactoring.
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +); + +const UseCases = () => { + const cases = [ + { + title: "Personalized Memory", + desc: "An MCP server that learns user preferences over time and stores them in the Actor state.", + }, + { + title: "Dev Environment", + desc: "A persistent cloud coding environment that an LLM can manipulate via MCP tools.", + }, + { + title: "Async Research", + desc: "Trigger a research task via MCP, let the Actor run for an hour, and notify the LLM when done.", + }, + { + title: "Team Knowledge", + desc: "A shared MCP server that multiple team members (and their LLMs) connect to simultaneously.", + }, + ]; + + return ( +
    +
    + + Unlock New Capabilities + +
    + {cases.map((c, i) => ( + +
    + +
    +

    {c.title}

    +

    {c.desc}

    +
    + ))} +
    +
    +
    + ); +}; + +const Ecosystem = () => ( +
    +
    + + Works with your stack + +
    + {["Claude Desktop", "Cursor", "Zed", "Vercel AI SDK", "LangChain", "Sourcegraph Cody"].map((tech, i) => ( + + {tech} + + ))} +
    +
    +
    +); + +export default function MCPPage() { + return ( +
    +
    + + + + + + + + {/* CTA Section */} +
    +
    + + Give your AI a brain. + + + Start building stateful MCP servers that actually remember. + + + + + +
    +
    +
    +
    + ); +} + diff --git a/website/src/app/(v2)/(marketing)/solutions/per-tenant-db/page.tsx b/website/src/app/(v2)/(marketing)/solutions/per-tenant-db/page.tsx new file mode 100644 index 0000000000..b790150ae7 --- /dev/null +++ b/website/src/app/(v2)/(marketing)/solutions/per-tenant-db/page.tsx @@ -0,0 +1,665 @@ +"use client"; + +import { useState } from "react"; +import { + Terminal, + Zap, + Globe, + ArrowRight, + Database, + Layers, + Check, + RefreshCw, + Shield, + Network, + FileJson, + Key, + Table2, + Moon, + Rocket, + Coins, + Gauge, +} from "lucide-react"; +import { motion } from "framer-motion"; + +// --- Shared Design Components --- +const Badge = ({ text, color = "violet" }) => { + const colorClasses = { + orange: "text-orange-400 border-orange-500/20 bg-orange-500/10", + blue: "text-blue-400 border-blue-500/20 bg-blue-500/10", + pink: "text-pink-400 border-pink-500/20 bg-pink-500/10", + violet: "text-violet-400 border-violet-500/20 bg-violet-500/10", + }; + + return ( +
    + + {text} +
    + ); +}; + +const CodeBlock = ({ code, fileName = "tenant.ts" }) => { + return ( +
    +
    +
    +
    +
    +
    +
    +
    +
    {fileName}
    +
    +
    +
    +					
    +						{code.split("\n").map((line, i) => (
    +							
    + + {i + 1} + + + {(() => { + const tokens = []; + let current = line; + + const commentIndex = current.indexOf("//"); + let comment = ""; + if (commentIndex !== -1) { + comment = current.slice(commentIndex); + current = current.slice(0, commentIndex); + } + + const parts = current.split(/([a-zA-Z0-9_$]+|"[^"]*"|'[^']*'|\s+|[(){},.;:[\]])/g).filter(Boolean); + + parts.forEach((part, j) => { + const trimmed = part.trim(); + + if (["import", "from", "export", "const", "return", "async", "await", "function"].includes(trimmed)) { + tokens.push({part}); + } else if (["actor", "Object", "assign", "push", "updateSettings", "addData"].includes(trimmed)) { + tokens.push({part}); + } else if (["state", "actions", "settings", "data", "newSettings", "item", "count", "length"].includes(trimmed)) { + tokens.push({part}); + } else if (part.startsWith('"') || part.startsWith("'")) { + tokens.push({part}); + } else if (!isNaN(Number(trimmed)) && trimmed !== "") { + tokens.push({part}); + } else { + tokens.push({part}); + } + }); + + if (comment) { + tokens.push({comment}); + } + + return tokens; + })()} + +
    + ))} +
    +
    +
    +
    + ); +}; + +const SolutionCard = ({ title, description, icon: Icon, color = "violet" }) => { + const getColorClasses = (col) => { + switch (col) { + case "orange": + return { + bg: "bg-orange-500/10", + text: "text-orange-400", + hoverBg: "group-hover:bg-orange-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(249,115,22,0.5)]", + border: "border-orange-500", + glow: "rgba(249,115,22,0.15)", + }; + case "blue": + return { + bg: "bg-blue-500/10", + text: "text-blue-400", + hoverBg: "group-hover:bg-blue-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(59,130,246,0.5)]", + border: "border-blue-500", + glow: "rgba(59,130,246,0.15)", + }; + case "pink": + return { + bg: "bg-pink-500/10", + text: "text-pink-400", + hoverBg: "group-hover:bg-pink-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(236,72,153,0.5)]", + border: "border-pink-500", + glow: "rgba(236,72,153,0.15)", + }; + case "zinc": + return { + bg: "bg-zinc-500/10", + text: "text-zinc-400", + hoverBg: "group-hover:bg-zinc-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(113,113,122,0.5)]", + border: "border-zinc-500", + glow: "rgba(113,113,122,0.15)", + }; + case "violet": + return { + bg: "bg-violet-500/10", + text: "text-violet-400", + hoverBg: "group-hover:bg-violet-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(139,92,246,0.5)]", + border: "border-violet-500", + glow: "rgba(139,92,246,0.15)", + }; + default: + return { + bg: "bg-violet-500/10", + text: "text-violet-400", + hoverBg: "group-hover:bg-violet-500/20", + hoverShadow: "group-hover:shadow-[0_0_15px_rgba(139,92,246,0.5)]", + border: "border-violet-500", + glow: "rgba(139,92,246,0.15)", + }; + } + }; + const c = getColorClasses(color); + + return ( +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +

    {title}

    +
    +
    +

    {description}

    +
    +
    + ); +}; + +const Hero = () => ( +
    +
    + +
    +
    +
    + + + + Persistent State for
    + Every Customer. +
    + + + Don't leak data between rows. Give every tenant their own isolated Actor with private in-memory state. Zero latency, instant provisioning, and total data sovereignty. + + + + + +
    + +
    +
    +
    + { + // Direct in-memory modification + // Persisted automatically + Object.assign(c.state.settings, newSettings); + return c.state.settings; + }, + + addData: (c, item) => { + c.state.data.push(item); + return { count: c.state.data.length }; + } + } +});`} + /> +
    +
    +
    +
    +
    +); + +const IsolationArchitecture = () => { + return ( +
    +
    +
    + + The Silo Model + + + In a traditional SaaS, one bad query from Tenant A can slow down Tenant B. With Rivet, every tenant lives in their own process with their own resources. + +
    + +
    +
    + + {/* The Router */} +
    +
    + +
    + Router +
    + + {/* Connection Lines */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Tenant Silos */} +
    + {/* Tenant A */} +
    +
    Tenant A
    +
    + +
    +
    +
    +
    + 12MB • Active +
    + + {/* Tenant B */} +
    +
    Tenant B
    +
    + +
    +
    +
    +
    + 1.4GB • Active +
    + + {/* Tenant C - Sleeping */} +
    +
    Tenant C
    +
    + +
    +
    +
    +
    + 0MB • Sleeping +
    +
    +
    +
    +
    + ); +}; + +const StateFeatures = () => { + const features = [ + { + title: "Zero-Latency Access", + description: "The state lives hot in memory on the same node as the compute. No network hop to external cache.", + icon: Zap, + color: "violet", + }, + { + title: "Instant Provisioning", + description: "Create a new tenant store in milliseconds. Just spawn an actor; no Terraform required.", + icon: Rocket, + color: "blue", + }, + { + title: "Schema Isolation", + description: "Every tenant can have a different state shape. Roll out data migrations gradually, tenant by tenant.", + icon: FileJson, + color: "pink", + }, + { + title: "Connection Limits", + description: "Stop worrying about Postgres connection limits. Each actor has exclusive access to its own isolated state.", + icon: Gauge, + color: "zinc", + }, + { + title: "Data Sovereignty", + description: "Easily export a single tenant's state as a JSON file. Perfect for GDPR takeouts or backups.", + icon: Shield, + color: "violet", + }, + { + title: "Cost Efficiency", + description: "Sleeping tenants cost nothing. You only pay for active CPU/RAM when the state is being accessed.", + icon: Coins, + color: "orange", + }, + ]; + + return ( +
    +
    + +

    State Superpowers

    +

    The benefits of embedded state with the scale of the cloud.

    +
    + +
    + {features.map((feat, idx) => ( + + + + ))} +
    +
    +
    + ); +}; + +const CaseStudy = () => ( +
    +
    +
    +
    + + + B2B CRM Platform + + + A CRM serving 10,000 companies. Each company has custom fields, unique workflows, and strict data isolation requirements. + +
      + {["Noisy Neighbor Protection: Large imports by Company A don't slow down Company B", "Custom Schemas: Enterprise clients can add custom fields instantly", "Easy Compliance: 'Delete all data for Company X' is just deleting one actor"].map((item, i) => ( + +
      + +
      + {item} +
      + ))} +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    Tenant: Acme Corp
    +
    DB Size: 450MB
    +
    +
    +
    Online
    +
    +
    +
    > GET /leads?status=new
    +
    < Result: 14,203 objects (Returned in 4ms)
    +
    +
    + +
    +
    +
    +); + +const UseCases = () => { + const cases = [ + { + title: "SaaS Platforms", + desc: "Give every customer their own isolated environment. Scale to millions of tenants effortlessly.", + }, + { + title: "Local-First Sync", + desc: "Serve as the authoritative cloud replica for local state on user devices.", + }, + { + title: "User Settings", + desc: "Store complex user preferences and configurations JSON in a dedicated actor, not a giant shared table.", + }, + { + title: "IoT Digital Twins", + desc: "One actor per device. Store sensor history and configuration state in a dedicated micro-store.", + }, + ]; + + return ( +
    +
    + + Built for Scale + +
    + {cases.map((c, i) => ( + +
    + +
    +

    {c.title}

    +

    {c.desc}

    +
    + ))} +
    +
    +
    + ); +}; + +const Ecosystem = () => ( +
    +
    + + Works with your stack + +
    + {["Drizzle", "Kysely", "Zod", "Prisma (JSON)", "TypeORM"].map((tech, i) => ( + + {tech} + + ))} +
    +
    +
    +); + +export default function PerTenantDBPage() { + return ( +
    +
    + + + + + + + + {/* CTA Section */} +
    +
    + + Isolate your data. + + + Start building multi-tenant applications with the security and performance of single-tenant architecture. + + + + + +
    +
    +
    +
    + ); +} + diff --git a/website/src/app/(v2)/(marketing)/templates/[slug]/page.tsx b/website/src/app/(v2)/(marketing)/templates/[slug]/page.tsx index 035d074496..7266e73b26 100644 --- a/website/src/app/(v2)/(marketing)/templates/[slug]/page.tsx +++ b/website/src/app/(v2)/(marketing)/templates/[slug]/page.tsx @@ -109,7 +109,7 @@ export default async function Page({ params }: Props) { vercelDeployUrl.searchParams.set('demo-url', `https://www.rivet.dev/templates/${template.name}`); return ( -
    +
    {/* Header with Image */}
    diff --git a/website/src/app/(v2)/(marketing)/templates/page.tsx b/website/src/app/(v2)/(marketing)/templates/page.tsx index e18f8b09b9..842ce50685 100644 --- a/website/src/app/(v2)/(marketing)/templates/page.tsx +++ b/website/src/app/(v2)/(marketing)/templates/page.tsx @@ -40,7 +40,7 @@ export default function Page() { return ( -
    +
    diff --git a/website/src/app/(v2)/[section]/layout.tsx b/website/src/app/(v2)/[section]/layout.tsx index acf6fbdb27..46d7b4a5f3 100644 --- a/website/src/app/(v2)/[section]/layout.tsx +++ b/website/src/app/(v2)/[section]/layout.tsx @@ -62,7 +62,7 @@ export default function Layout({ ) : null } /> -
    +
    {/* Links */} -
    +
    Product @@ -177,6 +189,24 @@ function SmallPrint() { ))}
    +
    +
    + Solutions +
    +
      + {footer.solutions.map((item) => ( +
    • + + {item.name} + +
    • + ))} +
    +
    Developers diff --git a/website/src/components/v2/Header.tsx b/website/src/components/v2/Header.tsx index 7e9db41075..3aa2b0c259 100644 --- a/website/src/components/v2/Header.tsx +++ b/website/src/components/v2/Header.tsx @@ -6,11 +6,11 @@ import { Header as RivetHeader } from "@rivet-gg/components/header"; import { Icon, faDiscord } from "@rivet-gg/icons"; import Image from "next/image"; import Link from "next/link"; -import { type ReactNode, useEffect, useState } from "react"; +import { type ReactNode, useEffect, useRef, useState } from "react"; import { usePathname, useRouter } from "next/navigation"; import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@rivet-gg/components"; import { faChevronDown } from "@rivet-gg/icons"; -import { Bot, Gamepad2, FileText, Workflow, Database } from "lucide-react"; +import { Bot, Gamepad2, FileText, Workflow, ShoppingCart, Wand2, Network, Clock, Database, Globe } from "lucide-react"; import { GitHubDropdown } from "./GitHubDropdown"; import { HeaderSearch } from "./HeaderSearch"; import { LogoContextMenu } from "./LogoContextMenu"; @@ -50,22 +50,17 @@ function TextNavItem({ function SolutionsDropdown({ active }: { active?: boolean }) { const [isOpen, setIsOpen] = useState(false); + const closeTimeoutRef = useRef(null); const solutions = [ { - label: "Agents", + label: "Agent Orchestration", href: "/solutions/agents", icon: Bot, description: "Build durable AI assistants" }, { - label: "Game Servers", - href: "/solutions/game-servers", - icon: Gamepad2, - description: "Authoritative multiplayer servers" - }, - { - label: "Collaborative State", + label: "Multiplayer Documents", href: "/solutions/collaborative-state", icon: FileText, description: "Real-time collaboration" @@ -77,24 +72,58 @@ function SolutionsDropdown({ active }: { active?: boolean }) { description: "Durable multi-step processes" }, { - label: "User-Session Store", - href: "/solutions/user-session-store", + label: "Vibe-Coded Backends", + href: "/solutions/app-generators", + icon: Wand2, + description: "Backend for AI-generated apps" + }, + { + label: "Geo-Distributed Databases", + href: "/solutions/geo-distributed-db", + icon: Globe, + description: "Multi-region state replication" + }, + { + label: "Per-Tenant Databases", + href: "/solutions/per-tenant-db", icon: Database, - description: "Isolated user data stores" + description: "Isolated state per customer" }, ]; + const handleMouseEnter = () => { + if (closeTimeoutRef.current) { + clearTimeout(closeTimeoutRef.current); + closeTimeoutRef.current = null; + } + setIsOpen(true); + }; + + const handleMouseLeave = () => { + closeTimeoutRef.current = setTimeout(() => { + setIsOpen(false); + }, 150); + }; + + useEffect(() => { + return () => { + if (closeTimeoutRef.current) { + clearTimeout(closeTimeoutRef.current); + } + }; + }, []); + return (
    setIsOpen(true)} - onMouseLeave={() => setIsOpen(false)} + className="px-2.5 py-2 opacity-60 hover:opacity-100 transition-all duration-200" + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} > @@ -105,9 +134,9 @@ function SolutionsDropdown({ active }: { active?: boolean }) { setIsOpen(true)} - onMouseLeave={() => setIsOpen(false)} - sideOffset={8} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} + sideOffset={2} alignOffset={0} side="bottom" > @@ -245,7 +274,7 @@ export function Header({ } breadcrumbs={
    - {/* */} + } breadcrumbs={
    - {/* */} + s.id === getCurrentSection());