Devora.js 0.3.2: what changed and what to do

Request access in loaders, actions that return data, error pages, a head API, cookie and proxy settings, CORS, and a fixed Vercel runtime — plus the changes an existing app has to act on.

0.3.2 came out of two products being built on 0.3.1 — YallaDo and Khepra — and the list of things they had to work around. It's a big release for a patch number: @devorajs/core and @devorajs/cli 0.3.2, the adapters 0.2.3, @devorajs/adapter-node published for the first time (0.2.1), and create-devora 0.3.1. Upgrade core and the CLI together.

Pages can read the request and answer forms properly

Loaders and actions now get ctx.url (query string included), ctx.headers and ctx.ip. An action can return data for the page, or fail() with a status, instead of only redirecting:

export async function action(formData: FormData, ctx: RequestContext) {
  ctx.verifyCsrf(formData);
  const text = String(formData.get("text") ?? "").trim();
  if (!text) return fail(400, { error: "Write something first." }); // re-render, status 400
  await addNote({ text }, ctx);
  return redirect("/notes");                                       // 303 See Other
}

That's from the multi-app example, where CI posts an empty note and checks for the 400. Thrown errors keep their status too — requireAuth() is a 401, verifyCsrf() a 403 — and render the app's error.tsx. Multipart uploads arrive as File objects, and there's a <head> API for stylesheets, fonts and meta tags. All of it is on Pages, forms and errors.

Deploying stopped being the hard part

  • The Vercel function runtime was hardcoded to nodejs20.x, which Vercel now rejects. It's picked from your Node version and engines now, and printed.
  • CommonJS dependencies like pg crashed the bundled function on its first request. They work now.
  • A top-level api/ folder collides with Vercel's own functions folder. New apps keep API routes in endpoints/ (apiDir); URLs stay under /api.
  • devora build --adapter runs preflight checks first (session secret and store, runtime, that folder), so a deploy that can't work fails in seconds.

Sessions behind proxies and across subdomains

Cookie secure (true, "auto", false), sameSite and domain are configurable, with trustProxy for apps behind nginx, Caddy or a load balancer. A login served over plain HTTP used to fail silently because the cookie was always Secure; now the server warns. Bearer-only APIs can turn the CSRF cookie off with csrf: false, and API routes get CORS:

// apps/api/app.config.ts — from the api-bearer example
export default defineApp({
  backendOnly: true,
  apiDir: "endpoints",
  csrf: false,
  cors: { origins: ["http://localhost:5173"], allowHeaders: ["content-type", "authorization"] },
});

What an existing app has to do

Most of the release is new options. These can change how an existing app behaves:

  • Redirects after a POST are 303 (were 302). Update tests that expect 302.
  • Page errors keep their status instead of becoming 500s.
  • The framework stylesheet is only added to pages that render PageShell. A page that relied on it without the shell needs head: { theme: true }.
  • .env files in the project root are loaded automatically, and devora start reads PORT and HOST.
  • Request bodies are capped at 10 MB by default (maxBodyBytes).

The full list, each with what to do, is on Upgrading to 0.3.2. The terminal got a new look too: devora dev prints every app's URLs first, then its routes, and --quiet keeps only the URLs.