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. Devora.js currently uses Vite for the dev server (one per app, which also loads and reloads
your server modules) and for the client build. 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 yet. Security is a default in the design (CSP, HSTS, CSRF, revocable sessions), not a certification. See SECURITY.md.
- 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.
Or start a brand-new standalone project, outside this repo:
npm create devora@latestnpm and Yarn work too, not just pnpm β see docs/history/VERIFICATION.md for what's actually been tested
under each.
Requirements: Node 20 or newer, React 18.3 or 19, Vite 5.4 or 6 (new projects start on React 18 and whichever Vite 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 enforced only where cookies are involved. 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.
- 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.3.5), which opens with the behavior changes.
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, and 0.3.5 is not yet verified on a live deployment by this project.
pnpm run test runs 498 Vitest tests in 50 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
- π 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