One project. Several apps. One backend and one login.
📚 Docs · 💬 Discord · 🧪 Try it in 15 minutes · 📦 npm
Devora.js is a React framework for projects with more than one app: one shared backend, one login, each app deployed on its own.
The idea: a project is more than one app. A marketing site, a product app and an admin panel live in one repo, share a core and a backend, and build and deploy independently. Each app gets its own render mode, its own auth mode and its own deploy target, without duplicating the plumbing between them.
How it works. By default Devora.js uses Vite for the dev server (one per app, which also loads and reloads
your server modules) and for the client build; an experimental engine of our own, Sekhem, replaces Vite when you
choose it (engine: "own", Node 22.15 or later; npm install --save-dev @devorajs/sekhem, and create-devora adds it for you). Routing, the render modes, sessions, the shared backend and the
production server (devora start, which runs without a Vite dev server) are Devora.js's own code.
// devora.config.ts — three apps, one backend, one login
export default defineProject({
apps: [
{ name: "marketing", dir: "apps/marketing", domain: "example.com", auth: "none" },
{ name: "dashboard", dir: "apps/dashboard", domain: "app.example.com", auth: "shared" },
{ name: "admin", dir: "apps/admin", domain: "admin.example.com", auth: "isolated" },
],
shared: { backend: "packages/backend", auth: "shared", sessions: { store: "memory" } },
});devora dev runs all three, devora build builds each one, and a function written once in
packages/backend is called from any of them.
Honest version, so you can decide whether it fits:
- Early, pre-1.0. The API can still change between minor versions (each change is listed in the changelog).
- Maintained by one person. Tested by the maintainer; there are no outside production users yet.
- No third-party security audit. None has been done. Security is a default in the design (CSP, HSTS, CSRF checks on cookie-authenticated writes from 0.4.0, revocable sessions), not a certification. See SECURITY.md and the security model.
- What was and wasn't verified is written down in VERIFICATION.md: real servers and real requests in CI, platform deploys checked by hand.
If you try it, I'd genuinely like to hear what breaks or what's missing: issues, PRs and Discord are all welcome. TESTING.md is a 15-minute guide for exactly that.
What is next, what is open and what is only a vision (desktop and native mobile apps, not started, no dates) is in ROADMAP.md.
Most React frameworks assume one app per project. The moment a real product needs a marketing site, a product app, and an admin panel — a pretty normal shape — you're either cramming them into one app with route groups, or maintaining separate repos that share nothing. Devora.js treats "one repo, several independently-deployed apps sharing a core" as the default, not a workaround bolted on later.
The other design choice: no implicit caching. Every render mode (ssr/ssg/csr/isr/
streaming) is an explicit, route-level opt-in — no hidden revalidation windows to
reverse-engineer, no undocumented build format standing between you and a plain reverse proxy if
you ever need one.
The three example apps from this repo, deployed by the maintainer. Which Devora.js version each
deployment runs isn't stated here; 0.3.5 is not yet verified on a live deployment by this project
(see docs/guides/DEPLOYING.md).
- Vercel: marketing · dashboard · admin
- Netlify: marketing · dashboard · admin
- Docs site, built with the published npm packages rather than this monorepo: devorajs-docs-docs.vercel.app
The same shared header/theme (<PageShell>) across all three apps — one design, not three ad
hoc ones.
Marketing (auth: "none" — no login anywhere on this app)

Dashboard (auth: "shared" — an island counter and a clientOnly() widget)

Admin (auth: "isolated" — its own session cookie, separate from the other two)

git clone https://github.com/hassanalsa3aka/devora.js.git
cd devora.js
pnpm install
pnpm exec devora dev --app=dashboardEach app gets its own port starting at 10000 (in devora.config.ts order), and devora dev
prints every app's URL and route table when it boots. To test from a phone on the same Wi-Fi, add
--host — it's off by default because it exposes the dev server to everyone on that network.
One experimental option: --experimental-dev-server (also DEVORA_EXPERIMENTAL_DEV_SERVER=1) runs a dev server of Devora's own instead of Vite. It is experimental, is the separate package @devorajs/sekhem (install it with npm install --save-dev @devorajs/sekhem; create-devora adds it to a project that chooses it), needs Node 22.15 or later, and is not the default: plain devora dev is Vite and is unchanged. It does not read vite.config.ts, so there are no Vite plugins, no PostCSS or Sass, no --host and no HTTPS (tsconfig path aliases, CSS modules and import.meta.glob are supported). Measurements are on the docs site's Performance page and in benchmarks/results/.
Or start a brand-new standalone project, outside this repo:
npm create devora@latestnpm, Yarn and pnpm all work. The commands for each:
| Task | npm | Yarn | pnpm |
|---|---|---|---|
| Create a project | npm create devora@latest |
yarn create devora (Yarn 4: yarn dlx create-devora) |
pnpm create devora (or pnpm dlx create-devora) |
| Install | npm install |
yarn install |
pnpm install |
| Dev servers | npm run dev |
yarn dev |
pnpm run dev |
| Build | npm run build |
yarn build |
pnpm run build |
| Serve the build | npm run start |
yarn start |
pnpm run start |
| Add a package to one app | npm install dayjs -w apps/app |
yarn workspace @project/app-app add dayjs |
pnpm --filter @project/app-app add dayjs |
| Add a package to the project root | npm install dayjs |
yarn add -W dayjs (Yarn 4: yarn add dayjs) |
pnpm add -w dayjs |
| Run a package script | npm run typecheck |
yarn typecheck |
pnpm run typecheck |
| Run the project's CLI once | npx devora list |
yarn devora list |
pnpm exec devora list |
@project/app-app stands for the name in that app's package.json.
What differs, as seen when these commands were run:
- npm 11 prints
Unknown project config "link-workspace-packages"on every command (the project's.npmrccarries a pnpm 9/10 setting) and anallowScriptswarning for esbuild. npm 10 prints neither. Everything works. - Yarn 1 warns about an unmet peer dependency (
react); a plainyarn addat the root asks for-W. - Yarn 4 prints peer-dependency warnings (
YN0002) aboutreact. A new project works with Yarn 4's default settings, becausecreate-devorawrites a.yarnrc.ymlwithnodeLinker: node-moduleswhen you pick Yarn (Yarn 4's default, Plug'n'Play, also ran the CLI in a test from the 0.4.0 build, once@devorajs/clideclaredesbuild; before that it stopped with@devorajs/cli tried to access esbuild). A project made bycreate-devora0.3.x needs that one-line file added before installing. - pnpm 9 and 10: a plain
pnpm addat the root asks for-w. - pnpm 11 and later (12.10 was tried) work with a new project, because
pnpm-workspace.yamlnow carrieslinkWorkspacePackages: trueandallowBuilds: esbuild: true. A project made bycreate-devora0.3.x fails to install on pnpm 11 and later (ERR_PNPM_FETCH_404for@devorajs/backend, thenERR_PNPM_IGNORED_BUILDS): use pnpm 9 or 10, or add those two keys to itspnpm-workspace.yaml.
What is covered. CI installs this repository's workspace under npm 10, Yarn 1 and pnpm 9.9 on every push; the tests and every app build run under pnpm only. A new project from the 0.4.0 create-devora was installed, run in dev, built, served, given a package and a script and the one-off command, by hand, on Node 20 and Node 22 with npm 10 and 11, Yarn 1.22 and 4.18 (through corepack, default settings) and pnpm 9.15, 10.34 and 12.10: all passed. Bun and Deno were not tested, so they are not listed as supported.
Requirements: Node 20.19 or newer for Vite 8 (checked on 20.20, 22.23, 24.22 and 26.13; Node 26.13 is the one we run and recommend, and Sekhem needs 22.15 or newer), React 18.3 or 19, Vite 8.3.7 or later in Vite 8 (new projects start on React 18 and the Vite 8 the install resolves).
Or start from one of the runnable examples: a multi-app project, a backend-only
API with Bearer login, a content site with ssg and islands, or live updates (WebSockets) with a separate
realtime server.
- Multi-app, one repo —
devora.config.tsdeclares each app;devora build/devora deployhandle each one independently. - SSR, SSG, CSR, ISR, streaming — set per route, explicitly. Streaming is GET-only (no
action), and on Netlify it's delivered as one buffered response rather than progressively. - Islands —
island(() => import("./Widget"))hydrates just that component; the rest of the page stays static HTML. - Sessions & CSRF built in — one opaque, server-side, revocable session primitive for both
browsers (HttpOnly cookie) and API/mobile clients (
Authorization: Bearer), with CSRF checked by default on cookie-authenticated writes, every method butGET/HEAD/OPTIONS(Bearer requests are exempt; a route opts out withexport const csrf = false). Three auth modes per app:shared,isolated, ornone(no sessions at all, for apps that don't need login, like a marketing site). Shared login across subdomains needsshared.sessions.cookie.domain; cookiesecure(true/"auto"/false),sameSite,trustProxybehind a proxy, session lifetimes,noindexandcsrf: falsefor Bearer-only APIs are configurable too. Session storage is bring-your-own — seepackages/backend/AUTH.md. - API routes speak JSON, always — files in the app's
apiDirfolder (endpoints/in new apps,api/by default for older ones) are served under/api/**; a thrown error is a{ message }JSON response with the right status (never an HTML error page), and an unmatched/api/**path is a JSON 404.corsinapp.config.tsanswers cross-origin preflights (off by default). - Pages that can read the request —
ctx.url(query string),ctx.headers,ctx.ipin loaders and actions; actions return data (fail(400, …)) or a 303redirect(); thrown errors keep their status and render the app'serror.tsx; multipart uploads (bodies capped bymaxBodyBytes, 10 MB by default); a<head>API; route CSS linked automatically. Seedocs/guides/PAGES.md. - Security headers on by default — CSP, HSTS, X-Frame-Options, overridable per app, and
X-Content-Type-Options: nosniffon every response, including static files..svgfiles are served with a sandboxing CSP; user-uploaded SVGs must still never be served from the app origin (Pages guide). - SEO — OG tags and an auto-generated
sitemap.xml, opt-in per app. - Dynamic routes —
routes/users/[id].tsxmatches/users/123, with the value available asctx.params.id.routes/docs/[...slug].tsxmatches/docs/a,/docs/a/b/cand so on, withctx.params.slug === "a/b/c"andctx.catchAll.slugthe array ([[...slug]]also matches/docs). A static route always wins over[param], which wins over a catch-all. Works for page routes and API routes. Seedocs/guides/PAGES.md. - Backend modules & generic API routes (v2) —
defineModule()organizes shared backend logic with no DI container, ever;apiRoute()handlers live in each app'sapiDirfolder, matched by the same file-based router as page routes — a payment-provider webhook finally has somewhere to live that isn't a page'sloader/action. - Middleware (v2) — native
withMiddleware()composition, plus optionalfromExpressMiddleware()/fromFastifyPlugin()adapters for the wider npm ecosystem, governed from one place per project. - Repo-splitting (v2) —
devora split/sync/statusconvert any app (or the shared backend) into a real git submodule with its full commit history preserved, for independent team ownership, without giving up the shared-core/shared-backend development model day to day. - Deploy targets — Vercel, Netlify, Docker, or a plain VPS with a generated nginx/Caddy
config (
devora start, or@devorajs/adapter-nodefrom your own entry point). Seedocs/guides/DEPLOYING.md. - A CLI that says what it does —
devora devprints every app's URLs, then its routes;--hostexposes dev on your LAN (with a warning),--routeslists every API route,--quietkeeps only warnings, errors and URLs.devora startreadsPORT/HOST;devora build --outrelocates Vercel output;devora add --kind=pages|apiscaffolds a pages app or a backend-only API app;prebuildinapp.config.tsruns codegen before a build;.envfiles in the project root are loaded automatically. - No built-in ORM or auth provider — bring your own (Prisma, Drizzle, Clerk, Lucia, whatever you already use).
What changed in each release, and what an existing app has to do about it: the newest section of
CHANGELOG.md (0.4.0), which opens with the breaking changes and how to migrate.
Once v2's surface (backend modules, API routes, middleware, repo-splitting, streaming) settled, it
went through a real internal audit — every finding below was independently reproduced (a forged
cookie, a crafted .gitmodules path, an oversized request body) before being fixed, not assumed
from a description:
- Cross-app session isolation is cryptographically real, not just cookie-name-based — the
app's cookie name (its auth scope) is bound into the HMAC that derives a session's store key, so
an
"isolated"app stays isolated from the shared app even if the two share a secret and a store, on the cookie and the Bearer transport. - Request bodies are capped (10MB default) across every API route and form action.
- Streaming connections time out (30s default) instead of holding a connection open indefinitely if a Suspense boundary never resolves.
- Path traversal closed in the repo-splitting CLI (a crafted
.gitmodules/devora.config.tspath could previously run git commands, or delete a directory, outside the project root) and in the ISR disk cache (a taintedgetStaticParams()value could previously write/delete outside its own output directory). devora startnow setsNODE_ENV=productionitself, matchingbuild/deploy— previously relying on the caller to have set it.
Not a substitute for a formal third-party audit (still planned once the package has real external users) — but real, verified hardening in its own right.
What is open, partial or not verified lives in one place: ROADMAP.md (status board
and findings table). The short version: API handlers set headers as a plain object (several
Set-Cookie headers and SSE are not supported), ssg/isr on a dynamic route need
getStaticParams(), isr regeneration on a serverless filesystem is unverified, you bring your own
database client, rate limiting is opt-in per route and per process, and 0.3.5 is verified live only for the documentation site (a pre-rendered single app on Vercel; multi-app, sessions, API
routes and Netlify are not) (nor are 0.4.0's Vercel/Netlify header rules).
pnpm run test runs 626 Vitest tests in 65 files: @devorajs/core units, plus integration tests
that boot real dev and production servers and send real HTTP requests (sessions, cookies, CSRF,
apiDir, uploads, error pages), the adapter runtime and CommonJS-banner checks, the preflight
checks, and the terminal formatter. CI also typechecks fresh create-devora projects against the
packed packages, smoke-tests the packed @devorajs/adapter-node, and builds every app for all
three targets. Docker, the proxy generator against a real nginx/Caddy, and live platform deploys
were checked by hand, not by a test. Full breakdown in docs/history/VERIFICATION.md. If something's broken,
please open an issue.
@devorajs/core— SSR/streaming request handling, sessions/CSRF, security headers, islands, backend modules, API routes, middleware, config loading.@devorajs/cli— thedevoracommand, includingsplit/sync/statusfor repo-splitting.@devorajs/adapter-node— a plain Node HTTP server for a production build (whatdevora startruns), for your own entry point.@devorajs/adapter-vercel@devorajs/adapter-netlifycreate-devora— scaffolds a new standalone project (npm create devora@latest).
- 📖 Architecture & design spec — v1 — the original vision doc
- 📖 Architecture & design spec — v2 — backend modules, API routes, middleware, repo-splitting, streaming
- 🗺️ Roadmap — what's done, in progress, and planned, with the detailed build log
- 🚀 Deploying — Vercel, Netlify, Docker, container platforms, self-hosted VPS, CI
- 📄 Pages, forms and errors — request access, action results, uploads, the head API
- 🛡️ Security model — defaults, what you must do, what it does not do (0.4.0)
- 🔐 Auth and sessions — session stores, cookies behind proxies, CSRF
- ✅ Testing & verification
- 📝 Changelog
- 📊 Benchmarks — the same pages in Devora.js, Next.js, Astro, SvelteKit and React Router; latest results
- 📚 Full docs site — built with the published packages, covers every feature with runnable examples: Getting started, Render modes, Live updates (WebSockets), Examples, Compare, Performance and the Blog
- 🔴 Live updates (WebSockets) — the supported pattern, with a runnable example
- ⏱️ Background jobs — BullMQ with Redis, a SQLite queue, node-cron; tested on one Node server
- 🧪 Try it in 15 minutes — for outside testers
- 🤝 Contributing · 🛡️ Security policy
- 💬 Discord
MIT