Skip to main content
General

Quickstart (Core)

Build and serve a Dynamic App with a development-only in-memory release store.

Prefer to read code? Clone the example repository. View on GitHub
Request Agent · Browser · API Your Node.js server Middleware Dynamic Apps router agentOS VM

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.

CoreRivet-backed package
Package@rivet-dev/dynamic-apps-core@rivet-dev/dynamic-apps
Build artifact storageProvide upload and download handlersStored automatically
Cache invalidation after updatesManual notification with watchActiveReleaseHandled automatically
Rivet namespace per appBring your own integrationCreated and connected automatically
Regions and scalingManaged by your hostManaged by Rivet
LifecycleExplicit dispose()Managed by the package
Best forCustom infrastructureBatteries 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.