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 ( +
+
+ {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;
+ })()}
+
+
+ ))}
+
+
+ {description}
+Primitives designed for AI code generation platforms.
+{c.desc}
+
+
+ {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;
+ })()}
+
+
+ ))}
+
+
+ {description}
+Primitives for building reliable background systems.
+{c.desc}
+
-
+
+
+
{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 */}
+
+
+ {/* Animated Activity Dots - full brightness overlay */}
+
+
+
+
+ );
+};
+
+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());