Skip to content
hassanalsa3akaPublic

About

React framework for projects with more than one app: a marketing site, a product app and an admin panel share one backend and one login, and each deploys on its own. SSR, SSG, ISR, CSR, streaming and islands. Pre-1.0.

Topics

Resources

Contributing

Security policy

Stars

6 stars

Watchers

0 watching

Forks

Latest commit

 

History

264 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Devora.js

Devora.js

CI npm License: MIT Discord

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.

Status

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's next

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.

Why

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.

Live demos

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).

Screenshots

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) marketing

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

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

Quickstart

git clone https://github.com/hassanalsa3aka/devora.js.git
cd devora.js
pnpm install
pnpm exec devora dev --app=dashboard

Each 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@latest

npm, 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 .npmrc carries a pnpm 9/10 setting) and an allowScripts warning for esbuild. npm 10 prints neither. Everything works.
  • Yarn 1 warns about an unmet peer dependency (react); a plain yarn add at the root asks for -W.
  • Yarn 4 prints peer-dependency warnings (YN0002) about react. A new project works with Yarn 4's default settings, because create-devora writes a .yarnrc.yml with nodeLinker: node-modules when 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/cli declared esbuild; before that it stopped with @devorajs/cli tried to access esbuild). A project made by create-devora 0.3.x needs that one-line file added before installing.
  • pnpm 9 and 10: a plain pnpm add at the root asks for -w.
  • pnpm 11 and later (12.10 was tried) work with a new project, because pnpm-workspace.yaml now carries linkWorkspacePackages: true and allowBuilds: esbuild: true. A project made by create-devora 0.3.x fails to install on pnpm 11 and later (ERR_PNPM_FETCH_404 for @devorajs/backend, then ERR_PNPM_IGNORED_BUILDS): use pnpm 9 or 10, or add those two keys to its pnpm-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.

Key features

  • Multi-app, one repo — devora.config.ts declares each app; devora build/devora deploy handle 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 but GET/HEAD/OPTIONS (Bearer requests are exempt; a route opts out with export const csrf = false). Three auth modes per app: shared, isolated, or none (no sessions at all, for apps that don't need login, like a marketing site). Shared login across subdomains needs shared.sessions.cookie.domain; cookie secure (true/"auto"/false), sameSite, trustProxy behind a proxy, session lifetimes, noindex and csrf: false for Bearer-only APIs are configurable too. Session storage is bring-your-own — see packages/backend/AUTH.md.
  • API routes speak JSON, always — files in the app's apiDir folder (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. cors in app.config.ts answers cross-origin preflights (off by default).
  • Pages that can read the request — ctx.url (query string), ctx.headers, ctx.ip in loaders and actions; actions return data (fail(400, …)) or a 303 redirect(); thrown errors keep their status and render the app's error.tsx; multipart uploads (bodies capped by maxBodyBytes, 10 MB by default); a <head> API; route CSS linked automatically. See docs/guides/PAGES.md.
  • Security headers on by default — CSP, HSTS, X-Frame-Options, overridable per app, and X-Content-Type-Options: nosniff on every response, including static files. .svg files 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].tsx matches /users/123, with the value available as ctx.params.id. routes/docs/[...slug].tsx matches /docs/a, /docs/a/b/c and so on, with ctx.params.slug === "a/b/c" and ctx.catchAll.slug the 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. See docs/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's apiDir folder, matched by the same file-based router as page routes — a payment-provider webhook finally has somewhere to live that isn't a page's loader/action.
  • Middleware (v2) — native withMiddleware() composition, plus optional fromExpressMiddleware()/fromFastifyPlugin() adapters for the wider npm ecosystem, governed from one place per project.
  • Repo-splitting (v2) — devora split/sync/status convert 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-node from your own entry point). See docs/guides/DEPLOYING.md.
  • A CLI that says what it does — devora dev prints every app's URLs, then its routes; --host exposes dev on your LAN (with a warning), --routes lists every API route, --quiet keeps only warnings, errors and URLs. devora start reads PORT/HOST; devora build --out relocates Vercel output; devora add --kind=pages|api scaffolds a pages app or a backend-only API app; prebuild in app.config.ts runs codegen before a build; .env files 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.

Security hardening (v2)

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.ts path could previously run git commands, or delete a directory, outside the project root) and in the ISR disk cache (a tainted getStaticParams() value could previously write/delete outside its own output directory).
  • devora start now sets NODE_ENV=production itself, matching build/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.

Current limitations

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).

Testing

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.

Packages

Docs

License

MIT

About

React framework for projects with more than one app: a marketing site, a product app and an admin panel share one backend and one login, and each deploys on its own. SSR, SSG, ISR, CSR, streaming and islands. Pre-1.0.

Topics

Resources

Contributing

Security policy

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages