Custom Routing and Runtimes
Most applications should use the standard file conventions:
src/pages/**/page.* creates pages, src/apis/**/api.* creates API routes,
and their directories determine the URL. Global middleware comes from
src/middlewares/middleware.*; HTTP method policies use
withMiddlewares(handler, middlewares) from @evjs/ev/api.
Use the alternatives on this page only when an application deliberately disables file discovery, maintains a programmatic SPA route tree, or uses the client and server runtimes directly.
Disable file conventions
File-convention discovery has one project-wide switch:
// ev.config.ts
import { defineConfig } from "@evjs/ev";
export default defineConfig({
conventions: false,
});
conventions: false disables all framework file discovery together:
page.*files and client routes undersrc/pages;api.*files and API routes undersrc/apis;- global
src/middlewares/middleware.*.
There are no separate switches for pages, API routes, or middleware. Do not
combine conventions: false with a routing declaration. When file
conventions are enabled, pages remain under src/pages and API routes remain
under src/apis.
Explicit SPA application.routes configuration, imported modules marked with
"use server";, and modules generated by plugins are not part of file
discovery. They remain available when conventions are disabled.
The direct runtime examples below are alternatives to framework-managed file routing; they do not introduce another automatically discovered entry file.
Programmatic browser apps
When the browser application maintains its own router and bootstrap, use the
client runtime directly. The application's bundler must build this entry;
evjs does not automatically discover or build src/main.tsx:
// src/main.tsx
import {
createApp,
createAppRootRoute,
createRoute,
Link,
Outlet,
} from "@evjs/client";
const rootRoute = createAppRootRoute({
component: () => (
<main>
<Link to="/">Home</Link>
<Outlet />
</main>
),
});
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: "/",
component: () => <h1>Home</h1>,
});
const app = createApp({
routeTree: rootRoute.addChildren([indexRoute]),
});
declare module "@evjs/client" {
interface Register {
router: typeof app.router;
}
}
app.render("#app");
This approach is independent of the framework's file-based page model.
Embed an Application in an existing React tree
Use app.createComponent() instead of app.render() when another React host
owns the DOM root. The handle contains a stable Component, its element, and
an idempotent dispose() function. The component renders the complete
Application, including its Router, QueryClientProvider, and configured wrappers;
creating the handle does not render or create a DOM root.
import { flushSync } from "react-dom";
const controller = new AbortController();
const application = app.createComponent({ signal: controller.signal });
// hostRoot is owned by the integrating application.
hostRoot.render(application.element);
// Remove the Application from the host tree before releasing its ownership.
flushSync(() => hostRoot.render(null));
application.dispose(); // controller.abort() also releases the handle.
Only one rendering owner is allowed. Call app.unmount() before switching from
DOM rendering to component mode, and remove the component and dispose its handle
before calling app.render() again. Disposing a handle or aborting its signal
does not unmount the external host's React tree. An already aborted signal is
rejected. The host and embedded Application must use the same React renderer.
Framework integrations using pagesApp.updateRuntime() must configure and await
history changes before acquiring a component handle. Queued or pending history
updates block acquisition, and an active component owner blocks history updates.
Route-only updates keep the outer component stable and retire the previous
Application handle after the replacement tree commits.
Programmatic server apps
Programmatic server apps use @evjs/server directly. They are runtime
primitives, not framework file-route inputs, so evjs will not scan source files
for createRoute() declarations.
// src/server.ts
import { createApp, createRoute } from "@evjs/server";
import { serve } from "@evjs/server/node";
const health = createRoute("/api/health", {
GET: async () => Response.json({ ok: true }),
});
const app = createApp({
routes: [health],
});
serve(app, { port: 3001 });
Run a programmatic server runtime as a normal Node, Fetch, Bun, Deno, or platform entry outside server file-route discovery.