Static SPA
Proves immutable asset serving, navigation fallback, and the buildless fast path.
Open appView sourceSmolForge / Forge Deploy
Forge Alpha
Forge Deploy serves static assets, HTTP, managed durable state, bounded realtime, and connected OpenNext applications without placing provider credentials in repository builds.
Cloudflare provides the runtime, storage, networking, and state primitives that make Forge possible. Forge Deploy adds a release model above those primitives so publishing is tied to repository truth rather than the contents of an operator’s current checkout.
| Deployment concern | Direct provider deployment | Forge Deploy |
|---|---|---|
| Source identity | The command publishes whatever code and generated assets are present in the invoking checkout. | Forge reads one exact pushed Git SHA and freezes its configuration, application code, and assets into one deployment. |
| Before production | A deploy command may update the live Worker and bundled frontend together. | Every successful build receives an immutable preview. Provider publication, preview readiness, and production activation remain separate states. |
| Stale releases | Preventing an older checkout from becoming production depends on release procedure. | Automatic activation rechecks that the exact SHA is still the configured branch head and that protection policy and required checks still pass. |
| Partial provider success | If the provider changes traffic but the client loses the final receipt, operators must determine which side committed. | Forge retains the activation intent. A retry recognizes the exact target already active, records the provider receipt, and completes the Forge pointer without switching traffic again. A different active version fails closed. |
| Rollback and evidence | Operators correlate Git, build output, provider versions, and traffic changes themselves. | Forge records source, configuration, build, provider, preview, and activation evidence together. Rollback moves the production pointer to a prior ready deployment without rebuilding or rewinding durable state. |
Forge's own API and web application use the same exact-SHA release path available to connected Cloudflare applications. Forge builds the candidate without provider credentials in repository code, inherits the explicitly authorized production bindings, and publishes an immutable Worker Version without changing traffic.
A platform administrator tests that candidate through its Forge preview, then promotes the exact recorded provider version. Forge requires Cloudflare’s unique per-version Preview URL for ordinary connected Workers and never stages those release previews into the production deployment. Forge’s Durable Object API uses the provider’s bounded exception: one 0%-traffic candidate is reviewable through a version override while production remains at 100%; publishing another candidate replaces that preview slot. Activation is serialized per Worker, rechecks the candidate’s frozen provider base immediately before the provider mutation, and records the provider deployment receipt. If Wrangler recovery advanced the provider while Forge was unhealthy, the next candidate freezes that observed version as its base and can safely bring Forge’s pointer current. If Cloudflare commits but the final receipt read fails, the same promotion can be retried: Forge recognizes the exact target already active and finishes its own pointer without changing traffic again. A stale candidate cannot silently replace a newer production release.
forge.smol.ai remains attached directly to the Forge API Worker; it is not routed through the user-content Sites edge. Wrangler administrator access remains the independent recovery path. Forge owns the normal release evidence and activation workflow, while provider-level rollback remains available if Forge itself is unhealthy.
This is the practical advantage over running a raw deploy command: Forge can apply its exact-source, immutable-candidate, guarded-activation, and audited-rollback contract to its own control plane without pretending the underlying Cloudflare runtime disappeared.
Install the authoring package for type hints and local validation.
npm install --save-dev @smolai/forge
Add forgeBuild.ts at the configured project root:
import { defineForge } from '@smolai/forge/config';
export default defineForge({
version: 1,
name: 'my-app',
app: {
entrypoint: 'src/server.ts',
assets: { directory: 'dist', fallback: 'index.html' },
},
routes: [
{ pattern: '/api/*', to: 'app.http' },
{ pattern: '/*', to: 'app.assets' },
],
});
Validate the same deterministic contract Forge will read, then push normally. This command validates the manifest only; it does not create or enable a Deploy project:
npx smolforge deploy check
git push origin main
Committing forgeBuild.ts is not the activation step. From the repository’s Sites tab, create the Deploy project, accept the current versioned Deploy Terms, and enable it. Source authorization, account access, and project access checks must all pass. A monorepo may enable several projects, each with its own root directory and root-relative forgeBuild.ts. A production-branch push schedules every enabled project independently; Forge does not infer changed paths or create an atomic release group. Each successful exact-SHA deployment receives an immutable preview URL.
Read this guide as raw Markdown without running JavaScript.
Keep forgeBuild.ts as the checked-in deployment contract without writing it by hand. Change the repository remote, push its current branch, then use the shared migration assistant in the CLI or the repository’s Sites tab.
git remote set-url origin https://forge.smol.ai/<owner>/<repo>.git
git push -u origin "$(git branch --show-current)"
sf migrate
Large repository pushes use the same Git protocol rather than a separate importer. Forge reads only the bounded receive command prefix in the API Worker, then streams the pack into a repository-scoped R2 multipart upload with four bounded parts in flight. After that durable handoff, its restricted native-Git runner streams the pack into a sandbox, validates and indexes it, skips immutable objects already stored for the repository, and stores only missing objects before the expected-head ref transaction. Heartbeats and receive-pack progress keep the client connection live during staging and native processing. The request path never buffers the whole pack in Worker memory. A disconnected or retried request cannot expose a partial repository, and abandoned staging objects are swept automatically. Forge does not reject migrations from GitHub’s repository-size estimate; each individual push is bounded at 490 MB (decimal bytes), safely below the provider request ceiling, and larger histories can be transferred incrementally.
The GitHub history importer preserves standard Git LFS pointer files but does not copy GitHub-hosted LFS object bytes. Upload those bytes separately with a standard client or Forge’s authorized LFS HTTP endpoints. Exact-SHA builds and the Sites adoption flow do not require a local git-lfs binary. The source-implemented administrator workflow inspects an exact SHA, reports missing objects, and opens an idempotent migration pull request only after standard-LFS bytes are verified; it requires a separate Forge release before live use. Its automatic Deploy preview is suppressed, and creating it does not advance production or deploy.
Clones use the symmetric bounded-memory path: graph walking reads only loose-object headers for blobs, while pack generation streams stored zlib bodies through decompression and recompression without expanding a large blob in Worker memory.
The assistant inspects repository files without running application code. It shows the exact branch and commit, explains detected package, framework, build, assets, and Cloudflare features, and previews a complete typed forgeBuild.ts.
sf migrate --json are read-only.--target http|assets.main or master exists, Forge uses it. If both exist, main wins and Forge warns about the ambiguous repository shape.For migration evaluation, a deployment may keep running as a connected Cloudflare Worker while Forge owns its unlisted preview hostname. Forge enables Cloudflare Preview URLs on the approved Worker while preserving its existing workers.dev setting, then proxies the unique per-version URL directly. A Worker for which Cloudflare cannot issue such a URL receives the bounded Durable Object behavior described below, never a falsely immutable production alias. Forge still checks repository and deployment eligibility on every request and applies no-store, security headers, response sanitization, noindex, and the Forge deployment receipt.
Import the checked-in Wrangler configuration as inert, validated data:
import { defineForge, importWrangler } from '@smolai/forge/config';
export default defineForge({
version: 1,
name: 'my-worker',
build: { command: 'npm run build' },
app: { entrypoint: '.output/worker.js' },
routes: [{ pattern: '/*', to: 'app.http' }],
provider: {
cloudflare: {
wrangler: importWrangler('wrangler.toml', {
shareProductionResources: true,
}),
},
},
});
app.entrypoint may name authored source or an already-bundled ESM framework output. Forge compiles the resulting module graph with its pinned toolchain. The module must be Workers-compatible and export an asynchronous fetch handler; Node HTTP server output is not accepted. SolidStart/Nitro applications should select the cloudflare_module preset before pointing Forge at .output/server/index.mjs.
Forge reads and validates the TOML from the exact commit without executing it. The explicit shareProductionResources acknowledgement is required because the preview inherits the connected Worker’s existing bindings and can therefore read or mutate the same D1, KV, R2, Durable Objects, secrets, and services.
Successful pushes and administrator-requested builds of the current production-branch head populate Forge’s content-addressed caches. Generated Deploy builds automatically select the frozen lockfile’s npm, pnpm, or Bun cache and may additionally reuse an exact dependency snapshot. Every dependency-cache identity includes the normalized Deploy project root, so sibling monorepo projects warm independently even when they share one lockfile. Package-manager download caches remain reusable across commits for that project. The exact snapshot is also keyed by immutable commit SHA because package lifecycle scripts and monorepo installs may depend on repository source. Forge creates the compressed snapshot immediately after its frozen install and before application build code runs. The 512 MiB default limit applies to that compressed payload, and Forge checks the expanded dependency size plus free workspace capacity before staging it. A measured project may receive a larger exact-snapshot entitlement without raising the separate 512 MiB manager-store ceiling. Oversized manager stores are safely bypassed rather than sharded or allowed to fail a deployment. The snapshot preserves every root and workspace-local node_modules tree without capturing repository source. Site source is checked out directly at the immutable commit instead of restoring a mutable workspace archive, so a stale source cache cannot delay or influence the build. For pnpm v9 monorepos, Forge uses the frozen importer index to discover the selected project and transitive declared workspace:* dependencies without walking unrelated siblings; unsupported syntax falls back to conservative full-tree discovery. Older immutable previews cannot write caches.
Full rebuild current SHA is the explicit same-commit warm-build canary and recovery path. It may restore exact source and exact dependencies for that unchanged SHA, deliberately bypasses finished OpenNext artifact reuse, and still executes the complete application build, validation, ISR seed, and provider publication. Runner logs record prebuild_reuse outcome=skipped reason=manual_full_build. Forge does not restore Next.js or OpenNext build output on that path. This is not an ordinary new-commit warm baseline: the full source SHA remains in dependency identity because lifecycle scripts can derive installed bytes from arbitrary repository source.
Declare every local package used by a pnpm monorepo application as a workspace:* dependency, including packages referenced only by framework configuration, Webpack aliases, build scripts, or code generators. Forge follows that declared graph when projecting an exact source subtree; undeclared siblings remain excluded even when configuration contains a relative path to them.
For OpenNext, an unchanged deployment can take a faster path before dependency installation. After exact-SHA checkout and configuration validation, Forge computes a versioned key over the source SHA, project/root, normalized manifest, Wrangler and lockfile digests, pinned OpenNext and Forge/provider adapters, runner image and compiler toolchain, connected provider target/base/policy/bindings, and asset entitlements. A hit still requires ready ISR evidence and a live immutable Cloudflare Worker Version, then creates a distinct project-scoped Forge release and preview pointer. Old or incomplete evidence fails closed to the normal frozen install and build. Forge does not require two fresh OpenNext builds to be byte-identical: Next.js draft-mode keys and concurrent prerender serialization can change harmless generated bytes while preserving the same canonical routes and assets.
After the build is quiesced, Forge first bounds raw OpenNext output as compiler input, permitting ordinary pnpm symlinks only when they resolve inside .open-next. Forge’s pinned, credential-free Wrangler compiler then creates the authoritative Cloudflare-compatible module graph and asset set. Module and compressed-size limits apply to that generated provider package, not to every traced compiler dependency. This preserves ESM and compiled WebAssembly semantics without giving Wrangler or repository code authority to publish or activate. Forge pins inheritance to the one active 100% production Worker version, validates its D1, KV, R2, Durable Object, and service identities against project policy, uploads assets, and creates an immutable Worker Version with deploy=false. Each version receives the reserved Forge-managed plain-text binding FORGE_SOURCE_SHA with the exact 40-hex release commit; repository configuration cannot override it. Forge enables and requires Cloudflare’s unique per-version Preview URL whenever the Worker supports it, so ordinary release previews never consume a production-deployment slot. Cloudflare-compatible WebAssembly modules are uploaded beside the generated ESM entrypoint with deterministic names and contribute to the reusable artifact identity. The compiled provider module graph may total up to 64 MiB raw and must remain at or below 10 MiB gzip. Trusted ingest counts binary streams before allocation. Verified public Forge Assets contribute their exact digest to the manifest without being hydrated or buffered during planning; provider-requested bodies are checksum-fenced and streamed as bounded base64 multipart data. Assets keep the 25 MiB per-file and 100,000-file provider ceilings; aggregate capacity is entitlement-driven up to 1 GiB per release. WebAssembly is capped at 10 MiB per module and 25 MiB total. Secret values are never read. A build declaration alone grants no route, DNS, schedule, custom-domain, or live-traffic authority.
Before upload, Forge hashes the compiled module, asset manifest, _headers and _redirects control files, complete Wrangler configuration, provider target, base version, binding generation, settings, and adapter version. A later deployment with the same effective bytes reuses the verified immutable Worker Version. Provider receipts are persisted before preview verification and deterministic tags reconcile interrupted responses.
Workers that define Durable Objects do not receive version-specific Preview URLs. Forge retains one bounded compatibility candidate at 0% and reaches it through a version override while production remains at 100%. Publishing another candidate replaces that slot, so historical Durable Object preview links are not immutable archives. Put the Durable Object behind a separately connected service when retained previews matter.
CLOUDFORGE_TRIGGER_SHA during every build. Framework clocks and random build IDs change the effective bytes and intentionally prevent provider-version reuse. SvelteKit applications should set kit.version.name to process.env.CLOUDFORGE_TRIGGER_SHA, with their existing CI or local Git SHA as a fallback. This preserves SvelteKit’s update identity and makes repeated builds of one commit reproducible; Forge never rewrites generated output.
Repository builds receive no DNS or traffic authority. After a release is reviewed, a project administrator may declare and explicitly apply a Custom Domain or path route against the project’s operator-approved Cloudflare account, Worker, and zones. The main Worker may own the hostname as a Custom Domain while more-specific archive Workers own path prefixes on that hostname.
Repository-wide project topology and routing proposals live in statically analyzable .forge/config.json; each project keeps its executable build contract in its root-relative forgeBuild.ts. The public JSON Schema defines exact v1 keys and defaults. Forge inspects the file from one exact SHA, shows project and provider-pattern changes, then requires a repository administrator to import that reviewed digest. New projects remain disabled. Import records desired bindings only; provider reconciliation still requires a separate explicit Apply.
{
"$schema": "https://forge.smol.ai/spec/deploy/v1/repository-config.schema.json",
"version": 1,
"defaults": { "production_branch": "main", "publication_mode": "manual" },
"projects": [{
"root": "apps/archive",
"slug": "archive",
"cloudflare": {
"zone_name": "example.com",
"hostname": "www.example.com",
"bindings": [{
"kind": "path_route",
"route_path": "/archive",
"match_mode": "reserved_prefix"
}]
}
}]
}
A path_route is one logical, wildcard-free prefix. The default exact_and_descendants mode reconciles host/archive and host/archive/* without claiming siblings. Explicit reserved_prefix instead reconciles host/archive*, covering query-bearing roots and descendants while also reserving siblings such as /archive-old. Forge displays that capture before import, persists the reviewed mode and provider receipts, and probes the query-bearing root plus a descendant. Retries repair partial control-plane import after a fresh inspection; missing configuration entries never detach existing projects or routes.
GET /api/repos/:owner/:repo/sites/repository-config is read-only. Its exact SHA, configuration digest, and plan digest fence the explicit import request. Import may create disabled projects and desired declarations, but it never enables a project, detaches omitted state, or writes Cloudflare. Apply remains authenticated, project-scoped, retryable, and auditable.
forgeBuild.ts v1 configurationThe file is typed TypeScript for editors, but Forge does not execute it. Forge statically reduces one data object from the exact commit before repository build code runs.
version is required and currently 1.build may declare one sandboxed command and working directory. Omit it for a buildless application.app declares an entrypoint, static assets, or both. app.runtime: "opennext-cloudflare" instead selects Forge’s OpenNext preset and forbids authored build, entrypoint, assets, and application limits.routes send exact or suffix-wildcard paths to app.http or app.assets.app.limits may lower Forge state and realtime ceilings; it cannot increase them.app.ai.profiles declares named, server-side Forge AI policies. Applications select a profile; Forge owns provider routing, credentials, and enforced ceilings.import { defineForge, importWrangler } from '@smolai/forge/config';
export default defineForge({
version: 1,
name: 'next-app',
app: { runtime: 'opennext-cloudflare' },
routes: [{ pattern: '/*', to: 'app.http' }],
provider: {
cloudflare: {
wrangler: importWrangler('wrangler.toml', {
shareProductionResources: true,
}),
},
},
});
defineForge import, one default call, literal data, comments, trailing commas, as const, and satisfies. All other imports, variables, functions, calls, spreads, filesystem, environment, network, clocks, and randomness are rejected.
forge.yml and forge.yaml are unsupported. Invalid configuration creates a visible failed deployment and does not start a repository build.
| Bound | v1 limit |
|---|---|
| Configuration source | 64 KiB |
| Syntax tree | 10,000 nodes, 64 literal levels |
| Normalized configuration | 256 KiB |
| Application bundle | 5 MiB |
OpenNext bounds raw compiler input at 150,000 entries, 125,000 files, 20,000 contained symlinks, 256 MiB per file, and 2 GiB total. The compiled provider package then uses a 2,000-file, 64 MiB raw module graph ceiling, separate asset/cache ceilings, and a 10 MiB compressed-Worker ceiling before connected-provider ingest. The project must declare @opennextjs/cloudflare from the registry and resolve it exactly through a frozen npm, pnpm, or Bun lockfile. Forge invokes only that installed project-local adapter and never fetches an implicit latest. Adapter v1 currently vets OpenNext 1.19.11 with Next >=15.5.18 <16 || >=16.2.6 <17.
Forge turns the declaration into the capabilities the application needs. Provider topology stays behind the Forge contract.
fetch method.env.forge.state.scope(name) with get, list, snapshot, and atomic mutate.export default {
async fetch(request, env) {
const counter = env.forge.state.scope('counter/main');
if (request.method === 'POST') {
return Response.json(await counter.mutate([
{ increment: { key: 'value', delta: 1 } },
{ emit: { topic: 'value', event: { type: 'incremented' } } },
]));
}
return Response.json(await counter.get('value'));
},
};
Production state persists across code deployments and rollbacks. Preview state is isolated by immutable deployment. Raw WebSocket handlers, repository-authored Durable Object classes, provider bindings, Node processes, and background daemons are not part of v1.
Forge AI Alpha exposes one logical model route, forge/text-fast@1, through named policies in app.ai.profiles. Provider credentials and tenant attribution remain in Forge. Native applications call env.forge.ai.responses.create(...); connected Cloudflare Workers call the private injected env.FORGE_AI.responsesCreate(...) RPC binding.
app: {
entrypoint: 'src/server.ts',
ai: {
profiles: {
commentary: {
model: 'forge/text-fast@1',
audience: 'server',
funding: ['host'],
maxInputTokens: 2500,
maxOutputTokens: 180,
privacy: { contentLogging: 'off' },
},
},
},
}
Calls are server-side, non-streaming, and require an 8–128 character idempotency key. JSON-schema profiles request and parse JSON, but the application must validate the generated object and retain a deterministic fallback. Browser code should call a narrow application-owned endpoint with its own authentication and abuse controls.
Each deployment receipt separates end-to-end time from trusted runner phases such as provisioning, exact-SHA checkout, dependency-cache restore, dependency installation, dependency snapshot save, application build, provider publication, and finalization. Successful, failed, and cancelled deployments use distinct duration labels. Reused assets and a reused runtime are reported as separate facts, because either optimization can occur without the other.
The Deploy page compares recent successful runner times with compact phase bars. Each row is labeled Fast or Full, while percentiles use only the newest measured deployment’s execution path and exact effective Forge configuration. P50 appears after three comparable deployments and P95 after twenty, over up to fifty recent records. The chart count and percentile sample size are reported separately. Comparisons exclude queueing, and performance-regression claims use the same comparison identity. Optimization signals call out evidence such as a dominant checkout, restore, install, or application-build phase and suggest the next bounded investigation. Recent runtime reuse is reported separately; Forge does not infer timings it did not record.
Phase timings are written by Forge-owned runner code, not parsed from repository logs. Older deployments and paths that did not record a phase simply omit that measurement rather than estimating it.
Preview artifacts expire after seven days. Forge protects running deployments, the active deployment, and the two newest successful rollback candidates, then removes expired immutable assets, Forge-managed runtime scripts, and Forge-published connected-account Worker Versions in retryable batches while retaining deployment, provider-receipt, and cleanup history. Connected version retirement shares the per-Worker activation fence, rechecks current Cloudflare deployment membership immediately before deletion, removes the exact expired candidate from a zero-percent preview slot when necessary, and treats an already-absent version as successful reconciliation. Cloudflare currently labels its per-version deletion endpoint beta, so provider failures leave the version and receipts intact and return cleanup to the bounded retry path. A 100-percent production version is never eligible. Forge never deletes the Worker, routes, domains, schedules, or bound KV, D1, R2, and Durable Object data.
Revoking source authorization, deleting or suspending the repository, or disabling the project immediately stops production and preview serving, state mutations, tickets, and realtime. Restoring eligibility never silently re-enables hosting.
Preview hostnames contain 128 bits of randomness, are unauthenticated, unlisted, and served with noindex. This is discovery resistance, not confidentiality. Do not place secrets or private data in a preview.
The native Forge runtime keeps deny-all egress. Connected Cloudflare applications retain the reviewed provider runtime policy and inherited bindings; those credentials and secret values never enter the repository build sandbox. Forge enforces the following native-runtime trial ceilings server-side:
| Resource | Ceiling |
|---|---|
| Realtime connections | 200 per project, 20 per preview, 50 per scope |
| Connection attempts | 60 per minute |
| Realtime events | 300 per minute per project |
| Event fan-out | 50 recipients per event |
| Event size | 8 KiB |
| Realtime transfer | 50 MiB per day |
| Session lifetime | 75 minutes |
Private builds use Forge’s internal exact Git objects and never pass clone credentials to the sandbox. Forge-authored secrets, unrestricted build networking, arbitrary Workers, customer-managed Cloudflare credentials, and repository-controlled provider authority remain outside this preview. A connected preview may intentionally inherit its approved production Worker’s resources, including existing provider-side secrets, without Forge reading their values.
Each fixture explains the platform behavior it proves and links to its SmolForge source.
Proves immutable asset serving, navigation fallback, and the buildless fast path.
Open appView sourceProves atomic durable mutation when multiple clients compete for one reservation.
Open appView sourceProves authoritative state, managed events, and synchronization across browser tabs.
Open appView sourceThe controlled-trial release gate on 2026-07-24 exercised the owning live surfaces, not only unit tests.
429 realtime_quota.Forge records exact source and configuration digests, build phases, provider versions, activation, access changes, rollback, invalidation, usage, and cleanup so those states remain distinguishable.
Pre-production boundarysites.smol.ai shares the smol.ai registrable domain with the Forge control plane. It is a real hosted trial, not the final general-availability isolation boundary.