@reckona/mreact-router
v0.0.16
Published
File-system app router, SSR, actions, and deployment adapters for mreact.
Maintainers
Readme
@reckona/mreact-router
@reckona/mreact-router is the mreact app router. It covers file-system routes,
loaders, metadata, server actions, prerendering, and deployment adapters.
Basic Usage
import { buildApp, renderBuiltAppRequest } from "@reckona/mreact-router";
await buildApp({
projectRoot: process.cwd(),
routesDir: "src/app",
publicDir: "public",
allowedSourceDirs: ["src"],
outDir: ".mreact",
targets: ["node"],
});
const response = await renderBuiltAppRequest({
outDir: ".mreact",
request: new Request("https://example.test/"),
});For application projects, configure the router explicitly in vite.config.ts:
import { defineConfig } from "vite";
import { mreactRouter } from "@reckona/mreact-router/vite";
export default defineConfig({
plugins: [
mreactRouter({
projectRoot: __dirname,
routesDir: "src/app",
publicDir: "public",
allowedSourceDirs: ["src"],
}),
],
});mreact-router build reads this config. Pass --target=node for Node, container, and AWS Lambda artifacts, --target=cloudflare for Workers artifacts, or configure buildTargets: ["node"] / buildTargets: ["cloudflare"] in mreactRouter() when one deployment target should be the project default. Without an explicit target, build output includes both Node-compatible server/client artifacts and Cloudflare route modules for backward compatibility. The legacy appDir shortcut remains
available for tests and older direct programmatic usage, but it is deprecated.
Use projectRoot + routesDir for new code. The shortcut is planned for
removal after 0.1.0.
mreact-router dev reads the same config and uses server.port from
vite.config.ts when PORT is not set. This keeps Playwright webServer
setups and local dev commands on the same configured port.
For TypeScript projects that type-check route modules directly, include the
app-router global declarations so route files can use <Slot /> without a
local import:
{
"compilerOptions": {
"types": ["@reckona/mreact-router/app-router-globals"]
}
}Client Navigation
Internal anchors are intercepted by the app-router client runtime and update the changed route payload instead of forcing a full document reload. The runtime keeps head metadata and route-data scripts synchronized, restores scroll on back/forward navigation, and prefetches client route scripts for likely navigations when the browser is not in reduced-data mode.
Use Link or linkProps() when a route needs explicit navigation behavior:
import { Link } from "@reckona/mreact-router/link";
export default function Page() {
return (
<nav>
<Link href="/docs" prefetch="viewport">
Docs
</Link>
<Link href="/editor" scroll="preserve" transition="auto">
Editor
</Link>
<Link href="/legacy" reload>
Legacy page
</Link>
</nav>
);
}Link and linkProps() are also re-exported from @reckona/mreact-router for
compatibility, but @reckona/mreact-router/link is the preferred import for
client-only code. Navigation observers are available from
@reckona/mreact-router/navigation-state as getNavigationState() and
subscribeNavigationState().
Route Module Exports
loader(context)returns data passed to the page component, or may return or throw aResponsefor redirects and custom responses.metadatainjects title, OpenGraph, viewport, and related head tags.generateStaticParams()returns dynamic route params to prerender.prerender = trueemits HTML at build time."use server"modules and<form action={...}>provide server actions.- Server actions reject
Content-Lengthvalues over10 MiBby default. PassserverActions: { maxBodyBytes }to configure the limit. - Route handlers may return or throw standard
Responseobjects from method exports such asGET,POST, orALL. Dynamic route handlers receive decoded params as the second argument:GET(request, { params }).
Deployment Adapters
@reckona/mreact-router/adapters/node: Nodehttpserver adapter.@reckona/mreact-router/adapters/static: static export adapter for prerendered routes.@reckona/mreact-router/adapters/edge: genericRequest/Responseruntime adapter.@reckona/mreact-router/adapters/cloudflare: Cloudflare Workers adapter.@reckona/mreact-router/adapters/aws-lambda: AWS Lambda HTTP API v2 adapter.
The built-in CLI can print compact request summaries for both local development and built output:
mreact-router dev --log=requests
mreact-router start .mreact --log=requests
MREACT_ROUTER_LOG=requests mreact-router devEach line includes method, path, status, duration, and runtime. Query strings, headers, and request bodies are intentionally omitted.
Server-only pages can opt into the lightweight navigation runtime without becoming hydrated client routes:
import { Link } from "@reckona/mreact-router/link";
export const navigationRuntime = true;
export default function Page() {
return <Link href="/docs" prefetch="viewport">Docs</Link>;
}The build manifest records this separately from client: true, emits a shared navigation runtime asset, prefetches client route scripts when present, and falls back to x-mreact-navigation: 1 HTML prefetches for server-only targets.
For Cloudflare Workers, combine createCloudflareBuiltRequestHandler,
createCloudflareStaticAssetLoader, createCloudflarePrerenderStore, and
createCloudflareRouteModuleRenderer. mreact-router build --target=cloudflare emits
.mreact/cloudflare/route-modules.mjs for non-prerendered and dynamic App
Router pages, so Workers entrypoints can import a plain route registry without
Vite-only import.meta.glob() transforms. Client assets are served only when
they appear in the generated manifest allow-list. Dynamic routes should resolve
modules through a build-time registry keyed by route.file, not by constructing
module ids from request input. Generated Cloudflare route modules support
stream = true pages with route-local <Await> boundaries and local
server-component imports.
For AWS Lambda, use createAwsLambdaRequestHandler() with API Gateway HTTP API
v2 or Lambda Function URL payload format 2.0:
import { createAwsLambdaRequestHandler } from "@reckona/mreact-router/adapters/aws-lambda";
export const handler = createAwsLambdaRequestHandler({
outDir: ".mreact",
importPolicy: {
allowedPackages: [
"@reckona/mreact",
// "cookie",
// "zod",
],
},
onResponse(response) {
response.headers.set("x-content-type-options", "nosniff");
},
});Production adapters enforce the app-router import policy when bundling loaders, middleware, route handlers, metadata, and server actions. Add every npm package imported by server-side application code to importPolicy.allowedPackages, including dependencies reached through app-local helper modules.
For Lambda and other Node-only deployments, build with mreact-router build --target=node or buildApp({ targets: ["node"] }). Node-only builds skip .mreact/cloudflare route modules, so loaders and server helpers may import Node-only dependencies such as database drivers without being bundled for the Workers runtime.
For Lambda deployments, package a minimal asset directory instead of the full project checkout. AWS Lambda enforces a 250 MB unzipped deployment package limit, and the runtime only needs .mreact/, the bundled handler, package.json / lockfiles, and production node_modules; src/, tests, dev dependencies, build caches, and Vite/Vitest/Playwright tooling are not required. mreact-router build --target=node keeps compiled server route artifacts in .mreact/server/server-modules/*.json instead of embedding them in one large server manifest. createAwsLambdaRequestHandler() treats outDir as read-only and materializes generated runtime files under /tmp/mreact-router/<hash>/runtime by default, with a node_modules symlink back to the deployed package root. Handler creation starts a background preload for the built runtime, loader modules, middleware, route handlers, and route metadata so route-specific bundling can move out of the first matched request on warmable runtimes; if a request arrives before preload finishes, middleware is resolved first, middleware responses or redirects return without loading the matched page artifact, and continuing requests load only the matched route's artifact closure. Pass runtimeDir only when you need to control that writable cache location. With pnpm, copy those files into .lambda/ and run pnpm --dir .lambda install --prod --frozen-lockfile --ignore-scripts --config.node-linker=hoisted. pnpm's default isolated linker is symlink-heavy, so verify the artifact's symlink count with find .lambda -type l | wc -l and measure actual file bytes in addition to du -sh .lambda before upload. Every package listed in importPolicy.allowedPackages must also be installed in that production artifact.
Set timings: true on createAwsLambdaRequestHandler() or createAwsLambdaStreamingRequestHandler() when you need low-overhead Lambda phase diagnostics. The adapter emits a router:request:timing debug log event with eventToRequestMs, runtimeDirMs, renderMs, and response serialization or streaming time, so production measurements can separate API Gateway event normalization, runtime materialization, route rendering, and Lambda response conversion.
The Lambda adapter returns proxy responses with cookies, headers,
statusCode, body, and isBase64Encoded. It buffers response bodies because
API Gateway and Lambda Function URL proxy responses do not expose true streaming
SSR. Prefer S3 + CloudFront for .mreact/client assets on production Lambda
deployments.
For Lambda Function URL response streaming, use
createAwsLambdaStreamingRequestHandler() instead:
import { createAwsLambdaStreamingRequestHandler } from "@reckona/mreact-router/adapters/aws-lambda";
export const handler = createAwsLambdaStreamingRequestHandler({
outDir: ".mreact",
});The streaming handler requires the Node.js Lambda runtime
awslambda.streamifyResponse() and awslambda.HttpResponseStream.from() APIs.
It streams response bytes directly and preserves status, headers, and cookies
through Lambda response streaming metadata.
Related APIs
renderAppRequest: development and test API for rendering a source app directory.renderBuiltAppRequest: production API for rendering a.mreact/build artifact.startDevServer: dev server that watches the app directory.startServer: helper that serves a.mreact/build artifact with Node.
renderAppRequest and the development server enforce the app-router import policy before bundling loaders, middleware, metadata, and server actions. Packages must either be explicitly allowed through importPolicy.allowedPackages or, in dev, be declared by the application package.json. Allowed server dependencies may use normal Node runtime features, including CommonJS modules that require Node built-ins such as events.
Use relative imports for app-local modules in server-side route code. The production server bundler applies the import policy before Vite-only or tsconfig path alias plugins can rewrite aliases such as ~/*, so an alias like ~/lib/csrf is treated as a package import named "~". Prefer ../lib/csrf.js or another relative specifier in loaders, middleware, route handlers, metadata modules, server actions, and their app-local helper modules.
Route pages may extract server-only UI into app-local .tsx or .mreact.tsx components and pass JSX children through them. The router compiles those local server-component dependencies with the same server string or stream target before inserting the page output into layout <Slot /> positions.
Sessions
Application code should import session helpers from @reckona/mreact-auth:
createMemorySessionStore(), createSession(), getSession(),
destroySession(), and rotateSession(). The router still re-exports these
helpers for older code, but those re-exports are deprecated.
