Upgrading to 0.3.5
0.3.5 makes devora start do less work per request (about a fifth to a third more requests per second on pages and API routes, measured on one machine), gives a missing session store or secret one clear message, and adds guides for live updates (WebSockets) and background jobs. Nothing in a typical app needs a change. If you are on 0.3.3 or older: 0.3.3 was prepared but never published, so its changes are in 0.3.4, and the 0.3.4 and 0.3.2 changes are listed below too.
Steps
- Upgrade
@devorajs/coreand@devorajs/clitogether, in the rootpackage.jsonand every app's:"^0.3.5". API routes run on the CLI's bundled copy of core and pages on the app's installed one, so mixing versions gives one project two behaviors. - If you import an adapter directly, use
@devorajs/adapter-node0.2.1,@devorajs/adapter-vercel0.2.3 or@devorajs/adapter-netlify0.2.4 (devorabundles the adapters, so most projects don't depend on them at all). Earlier adapter-node 0.2.0 and the 0.2.2 adapters were published without their build output and are deprecated. - Install, then run
npm run typecheck,npm run devandnpm run build. - Go through the tables below and change what applies to your app.
TypeScript: ctx.params keeps its type (Record<string, string>), so existing code compiles unchanged. A catch-all's segments are joined with / there, and ctx.catchAll.slug (or req.catchAll.slug in an API route) is the string[].
From 0.3.4 to 0.3.5
| What changed | What to do |
|---|---|
A missing session store, or (in production) a missing DEVORA_SESSION_SECRET, stops devora start with one clear line and exit status 1 instead of a stack trace. It refused to start before and still does. | Nothing to do. Add sessions: { store: "memory" } under shared in devora.config.ts (one long-running server) or a store backed by your database, and set the secret. |
ctx.url and ctx.headers are built the first time a handler reads them. Reading them is unchanged (the same URL and Headers objects every time). They are now getters, so assigning to them throws in strict mode. | Nothing to do unless your code assigns ctx.url or ctx.headers; the types never marked them read-only and nothing in the framework assigns them. |
| A rebuilt island or client-route manifest is picked up within a second, instead of on the next request. | Nothing to do, as built static files already work this way. |
The header's brand link has the accessible name devora.js home (it was Home, which did not contain its visible text). | A test that selects it by aria-label="Home" needs the new name. |
From 0.3.2 to 0.3.4
| What changed | What to do |
|---|---|
A file named [...name].tsx or [[...name]].tsx is now a catch-all route. Before, it was a single [param] whose name started with dots. | If you have such a file, rename it, or expect it to match more URLs (/docs/a/b/c, not only /docs/a). Nothing else needs to change: ctx.params values are still strings. |
A URL path containing a dot no longer ends at the static-file lookup. If no public file exists at that path, it goes on to route matching (so /docs/v1.2/intro can reach a catch-all). | Nothing to do. A real file under the public folder still wins. |
getStaticParams() values are percent-encoded in the output path: a value with a space or non-ASCII characters is written under its encoded path, which is what url.pathname holds. | Nothing to do unless you read dist/static by hand. |
sitemap.xml now lists the URLs of dynamic routes that export getStaticParams(). | Nothing to do; check the file if you post-process it. A dynamic route without getStaticParams() is still left out. |
devora start answers any method other than GET and HEAD to a public file, a built asset or a pre-rendered (ssg) page with 405 and Allow: GET, HEAD. It used to return the file. | A form that posts to a pre-rendered page never ran an action there; post to an ssr route instead. |
Pre-rendered pages are sent with Cache-Control: public, max-age=0, must-revalidate and an ETag (a repeat visit gets a 304). Pages and files are held in memory — 64 MB in total, 2 MB per file, larger ones streamed — and re-checked against the disk once a second. | Nothing to do. A rebuild into a running server shows up within a second; changed route code still needs a restart. See Deployment. |
With sitemap off, a hand-written public/sitemap.xml is served. It used to 404 in production. | Nothing to do. Turn sitemap on if you want the generated one (it takes precedence). |
From 0.3.1 to 0.3.2
0.3.2 was a large release for a patch number: request access in loaders, actions that return data, error pages, a <head> API, uploads, cookie and proxy settings, CORS, .env loading and a fixed Vercel runtime. Most of it is new options; this is what can change how an existing app behaves.
| What changed | What to do |
|---|---|
| Redirects after a POST are 303 See Other (were 302); loader redirects stay 302. | Browsers behave the same. Tests or clients that assert 302 after a form post: update them, or pass redirect(to, 302). |
Page errors keep their status: a thrown HttpError (or anything with a 4xx/5xx status) is that status, not a 500 — requireAuth() is 401, verifyCsrf() 403. Errors and unmatched URLs render error.tsx or a built-in page, in dev too. | Anything that treated every page failure as a 500 should check the status. Add an error.tsx for your own error page. |
The framework stylesheet and favicon are only added to pages that render PageShell or AppHeader (and CSR shells). | A page that relied on the theme without the shell needs head: { theme: true } in app.config.ts or its meta().head. |
PageShell wraps its content in <div class="devora-shell">; the theme no longer sets body { display: flex }. | Adjust custom CSS that depended on body being the flex container, or on main being a direct child of #root. Props are unchanged. |
The theme no longer defines --devora-bg-elevated (it has --devora-surface and more tokens). Not in the CHANGELOG; found upgrading this site. | If your CSS uses it, replace it with var(--devora-surface) or define it yourself: :root { --devora-bg-elevated: var(--devora-surface); }. A manual light/dark switch that overrides tokens should override the new ones too. |
A backend-only app answers GET / with 200 (JSON for API clients, a small HTML page for browsers). It was a 404. | Monitors that expected 404 there are fine to keep using /api/health. |
Request bodies are limited to 10 MB by default (maxBodyBytes, forms and API routes); larger ones get a 413. Malformed multipart is a 400. | Apps that accept larger uploads set maxBodyBytes in app.config.ts. |
.env files in the project root are loaded by dev, build, start and deploy; variables already in the environment win. | Check the project root for stale .env files, and drop node --env-file wrappers. |
devora start reads PORT and HOST (--port/--host still win). | A PORT left in the environment now moves the server; unset it or pass --port. |
devora build --adapter= anything other than vercel/netlify fails, instead of building without the requested output. | Scripts using --adapter=node drop the flag: devora build, then devora start. |
The Vercel runtime is no longer nodejs20.x (Vercel rejects it): vercel.runtime, else the running Node major, else engines.node, else nodejs24.x. | Nothing, unless you need a specific runtime. An engines.node no supported runtime satisfies fails the build. |
adapter-vercel empties .vercel/output before writing it. | Don't keep files of your own in that directory. |
The generated Vercel and Netlify functions trust the platform's proxy headers (trustProxy: true unless the app sets false). | Nothing, unless the function can be reached without going through the platform's edge. |
devora build only imports ssg/isr routes (and routes whose render mode isn't a string literal). | Nothing, unless a build relied on a request-time route running at build time. |
Unsupported island() calls (island(loader), a computed path, a path that doesn't resolve) fail the build and dev server with the file and line. | Fix the reported call to island(() => import("./path")). |
Session config that used to be ignored is an error: session settings on an auth: "none" app, a cookie.domain that doesn't cover the app, per-app lifetimes on a shared app. | Fix or remove the setting the error names. |
devora dev's boot output has a new layout: every app's URLs first, then routes per app; more than 10 API routes per app are counted. | Anything that parsed the old output should use --routes and match the new lines. |
Before deploying to Vercel, check the preflight output of devora build --adapter=vercel (runtime, a top-level api/ folder, session secret and store), and see Deployment.
Worth adopting while you're here
Move API routes out of a top-level api/ folder if you deploy to Vercel (apiDir: "endpoints"), set trustProxy behind nginx/Caddy, and replace hand-rolled query-string parsing with ctx.url. See Pages, forms and errors and Backend.
The complete list per package is in the CHANGELOG.