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
| Target | Best for | Trade-offs |
|---|---|---|
| Vercel Edge | Global 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 Workers | Global edge with KV/Durable Objects. Generous free tier. | No node:*, 128MB memory cap, cold-start ~5ms. |
| Cloudflare Pages | Static host + edge functions for SSR. Cheapest production deploy. | Limited to Pages Functions API; less flexibility than Workers. |
| Node.js | Long-running tasks, native modules, streaming SSR with backpressure. | You own the box. No global edge unless you fan-out yourself. |
| Docker | Kubernetes, ECS, your own infra. Reproducible builds. | Slower cold start, more ops overhead. |
Vercel Edge
pnpm add -D @jorvel/adapter-vercel
jorvel deploy --target vercel
vercel deployThe 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.
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:
{
"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>/.
jorvel deploy --target github-pages
# writes .github/workflows/pages.yml + apps/<host>/dist/.nojekyllPutting 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.
{
"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:
- Stateless handlers.Don't cache anything in process memory; use KV/Redis with a regional reader and a primary writer.
- Pin auth. If your auth provider is single-region, regional handlers still need to call back to that origin. Cache the verification result aggressively.
- 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.
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:
| Variable | Used by | Purpose |
|---|---|---|
NODE_ENV | All | Production-mode defaults (caching, error verbosity). |
PORT | Node, Docker | Listen port. |
JORVEL_DEBUG | All | Verbose logs. |
JORVEL_REMOTE_TIMEOUT_MS | All | Per-remote load timeout (default 10s). |
