JORVEL
Deploy

Deployment

jorvel deploy dynamically loads the right adapter package — Vercel Edge, Cloudflare, or Node — and scaffolds a working platform config. Adapters are loose deps; install only what you actually ship. Every adapter wraps the same edge-runtime-safe core from @jorvel/ssr/edge, so the behavior is consistent across platforms — the adapter only handles request/response translation and platform-specific bootstrapping.

Picking a target

TargetBest forTrade-offs
Vercel EdgeGlobal low-latency SSR with minimal ops. Built-in CDN, preview deploys.50ms CPU per request, no long-running tasks, vendor lock-in on KV.
Cloudflare WorkersGlobal edge with KV/Durable Objects. Generous free tier.No node:*, 128MB memory cap, cold-start ~5ms.
Cloudflare PagesStatic host + edge functions for SSR. Cheapest production deploy.Limited to Pages Functions API; less flexibility than Workers.
Node.jsLong-running tasks, native modules, streaming SSR with backpressure.You own the box. No global edge unless you fan-out yourself.
DockerKubernetes, ECS, your own infra. Reproducible builds.Slower cold start, more ops overhead.

Vercel Edge

bash
pnpm add -D @jorvel/adapter-vercel
jorvel deploy --target vercel
vercel deploy

The adapter forwards request.body and signal, lowercases headers, and returns a ReadableStream for streaming SSR. Static assets are served with Cache-Control: public, max-age=31536000, immutable.

api/[[...slug]].ts
ts
import { createVercelHandler } from '@jorvel/adapter-vercel';
import { App } from '../src/App';
import template from '../src/template.html?raw';
import routes from '../src/jorvel.routes';

export const config = { runtime: 'edge' };

export default createVercelHandler({ App, template, routes, etag: true });

Edge config (recommended)

The scaffold writes vercel.json with rewrites that route every URL through the edge handler, while letting the CDN serve hashed assets:

vercel.json
json
{
  "rewrites": [
    { "source": "/_assets/(.*)", "destination": "/_assets/$1" },
    { "source": "/(.*)",         "destination": "/api/ssr" }
  ],
  "headers": [
    {
      "source": "/_assets/(.*)",
      "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
    }
  ]
}

Streaming SSR on Vercel

The handler returns a ReadableStream when the SSR call streams; Vercel Edge passes it through. Pair with renderRouteToReadableStream from @jorvel/ssr/edge for progressive rendering.

GitHub Pages (static export)

For a static-export site (SPA host + prebuilt remotes on the same origin), jorvel deploy --target github-pages emits a Pages deploy workflow + .nojekyll. Enable Pages (Settings → Pages → Source: GitHub Actions) and push to main. For a project page, set a base path: jorvel build --base /<repo>/.

bash
jorvel deploy --target github-pages
# writes .github/workflows/pages.yml + apps/<host>/dist/.nojekyll

Putting remotes on a CDN

Each remote app is a self-contained bundle under apps/<name>/dist/. Upload that directory to a CDN and point federation.publicPathat it before building. The host's federation config uses the CDN URLs at runtime; users get cached, geo-routed remoteEntry files.

jorvel.config.json
json
{
  "federation": {
    "publicPath": "https://cdn.acme.com/dashboard/",
    "sri": { "algo": "sha384" },
    "allowlist": ["https://cdn.acme.com"]
  }
}

Multi-region SSR

For latency-sensitive apps with a global user base, deploy the host to multiple regions and let the platform's smart-routing pick the closest. Three rules:

  1. Stateless handlers.Don't cache anything in process memory; use KV/Redis with a regional reader and a primary writer.
  2. Pin auth. If your auth provider is single-region, regional handlers still need to call back to that origin. Cache the verification result aggressively.
  3. Replicate remotes.Push every remote to a global CDN. Skipping this turns the multi-region setup into "fast host, slow remote".

Writing your own adapter

Each adapter is just a thin bridge that turns the platform's native request type into EdgeRequest. Implement scaffoldDeploy() + a handler factory and jorvel deploy --target your-adapter will pick it up.

@your-co/jorvel-adapter-foo/src/index.ts
ts
import { createEdgeAdapter } from '@jorvel/ssr';

export const deployTarget = 'foo';

export async function scaffoldDeploy(opts: { cwd: string; dryRun?: boolean }) {
  // Write your platform config here.
  return { files: [], nextHint: 'foo deploy' };
}

export function createFooHandler(options) {
  const handler = createEdgeAdapter(options);
  return async (request) => {
    const res = await handler(toEdgeRequest(request));
    return toFooResponse(res);
  };
}

Environment variables

Adapters read a small set of envs at boot:

VariableUsed byPurpose
NODE_ENVAllProduction-mode defaults (caching, error verbosity).
PORTNode, DockerListen port.
JORVEL_DEBUGAllVerbose logs.
JORVEL_REMOTE_TIMEOUT_MSAllPer-remote load timeout (default 10s).