Quickstart (Core)
Build and serve a Dynamic App with a development-only in-memory release store.
When to use each
Use Core when your application owns release storage and deployment lifecycle. For managed storage and invalidation, use the standard Dynamic Apps Quickstart.
| Core | Rivet-backed package | |
|---|---|---|
| Package | @rivet-dev/dynamic-apps-core | @rivet-dev/dynamic-apps |
| Build artifact storage | Provide upload and download handlers | Stored automatically |
| Cache invalidation after updates | Manual notification with watchActiveRelease | Handled automatically |
| Rivet namespace per app | Bring your own integration | Created and connected automatically |
| Regions and scaling | Managed by your host | Managed by Rivet |
| Lifecycle | Explicit dispose() | Managed by the package |
| Best for | Custom infrastructure | Batteries included and scalable |
Quickstart
Install
Use Node.js 22 or newer:
npm add @rivet-dev/dynamic-apps-core @rivet-dev/dynamic-apps @hono/node-server hono
npm add --save-dev tsx
Create the instance
Create the Dynamic Apps instance and provide storage hooks. This example uses in-memory maps to keep the setup small:
import { serve } from "@hono/node-server";
import {
type ActiveRelease,
createDynamicApps,
} from "@rivet-dev/dynamic-apps-core";
import { Hono } from "hono";
// Development only: releases disappear on restart and updates cannot reach
// another process. Use durable storage and cross-process invalidation in production.
const active = new Map<string, ActiveRelease>();
const listeners = new Map<string, Set<() => void>>();
const dynamicApps = createDynamicApps({
async publishRelease(input) {
const release: ActiveRelease = {
appId: input.appId,
release: input.buildId,
artifact: {
...input.artifact,
bytes: new Uint8Array(input.artifact.bytes),
},
maxRequestBytes: 1024 * 1024,
maxResponseBytes: 4 * 1024 * 1024,
};
// The complete artifact is stored before this single active-map update.
active.set(input.appId, release);
for (const invalidate of listeners.get(input.appId) ?? []) invalidate();
return { appId: input.appId, release: release.release };
},
async loadActiveRelease(appId) {
const release = active.get(appId);
return release
? {
...release,
artifact: {
...release.artifact,
bytes: new Uint8Array(release.artifact.bytes),
},
}
: undefined;
},
async watchActiveRelease(appId, invalidate) {
const appListeners = listeners.get(appId) ?? new Set();
appListeners.add(invalidate);
listeners.set(appId, appListeners);
return () => {
appListeners.delete(invalidate);
if (appListeners.size === 0) listeners.delete(appId);
};
},
});
This in-memory store is development-only. It loses releases on restart and cannot invalidate another process. Use durable storage and cross-process notifications in production.
Set up the router
Mount appsRouter wherever generated apps should be served, then start the
HTTP server:
const app = new Hono();
app.route("/apps", dynamicApps.appsRouter);
const port = Number(process.env.PORT ?? 3000);
serve({ fetch: app.fetch, port });
console.log(`Dynamic Apps Core listening on http://localhost:${port}`);
Generate and deploy
An LLM writes the app as a set of files. Pass the skills
from @rivet-dev/dynamic-apps as the system prompt so the model knows the
supported project layout:
import { anthropic } from "@ai-sdk/anthropic";
import { rivetActorsSkill, webServerSkill } from "@rivet-dev/dynamic-apps";
import { generateObject } from "ai";
import { z } from "zod";
const { object } = await generateObject({
model: anthropic("claude-sonnet-5"),
// Skills teach the model the supported project layout.
system: [webServerSkill, rivetActorsSkill].join("
"),
schema: z.object({ files: z.record(z.string(), z.string()) }),
prompt: "Build a team board.",
});
const { files } = object;
Pass the generated files to deployApp():
await dynamicApps.deployApp({
appId: "team-board",
files,
});
Visit the app
Run the complete example and make a request:
npm start
curl http://localhost:3000/apps/team-board/
Core builds the app, publishes the release through your hooks, and loads it on
the first request. Warm requests reuse the cached agentOS VM. Call
await dynamicApps.dispose() when your host shuts down.
Read Core before running it across multiple processes.