@r-machine/next
v1.0.0-beta.1
Published
Next.js App Router integration for R-Machine — the TypeScript resource layer.
Maintainers
Keywords
Readme
@r-machine/next — R-Machine for Next.js App Router
A TypeScript resource layer for React and Next.js
Getting started
R-Machine ships an agent skill that scaffolds a project and adds resources. Start from a fresh app and install it:
npm create next-app@latest my-app
cd my-app
npx rforge@latest skillUsing pnpm, yarn or bun? Replace
npx rforge@latestwithpnpm dlx rforge@latest,yarn dlx rforge@latestorbunx rforge@latest. R-Machine itself has no package manager preference — the skill installs the packages with whichever one your project uses.
Then prompt your agent:
Install R-Machine in this projectFrom there, just describe a feature in plain words:
Add a counter to the home page: a label showing the current value,
and two buttons, "Increase" and "Decrease".
Disable "Decrease" when the value is 0.A step-by-step quickstart is coming on rmachine.dev.
Packages
| | Package | Description |
| -------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| | r-machine | The core: atlas, composers, plugs. Every project needs it. |
| | @r-machine/react | React integration. Install it in every project that renders React, Next.js included. |
| This package | @r-machine/next | Next.js App Router on top of the above: three routing models, the locale proxy, path composition. |
| | @r-machine/testing | mockPlug and verifyResourceAtlas. A dev dependency, and the recommended way to test resources. Warning: this package is still in active development — the API may change before the stable release. |
| | rforge | Command-line interface for R-Machine |
npm install r-machine @r-machine/react @r-machine/next
npm install -D @r-machine/testingDocumentation
llms-full.txt — the full API reference, written
to be read by an agent. Hand it over and ask what you'd ask a colleague who knows the
library: "how does OuterGear work?", "how would I do X here?"
Each example below is a working app you can clone and run.
| Example | Description |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| next | Next.js App Router |
| next-with-app-flat-strategy | Next.js App Router with cookie-based locale detection |
| next-with-app-origin-strategy | Next.js App Router with origin-based routing |
| next-with-app-path-strategy | Next.js App Router with path segment routing |
| next-with-app-path-strategy-no-proxy | Path strategy without proxy |
| react | React + Vite |
Core concepts at a glance
Shell — locale-aware content
A Shell is a multi-locale resource: one canonical file per locale, exact-keyed type validation across variants.
// r-machine/pub/shell/common/en.tsx (canonical — defines the shape)
import { type RShape } from "@/r-machine/setup";
export const r = { greeting: "Hello", addButton: "Add" };
export type Shell_Common = RShape<typeof r>;// r-machine/pub/shell/common/it.tsx (variant — type-checked against canonical)
import { localized } from "@/r-machine/setup";
export const r = localized("shell/common", {
greeting: "Ciao",
addButton: "Aggiungi",
});Gear — logic and state
A Gear is a stateful or stateless logic unit. Three flavors (InnerGear, BaseGear, OuterGear) differ only in scope and who can consume them (server side / client side). A stateful example:
// r-machine/pub/outer/counter.ts
import { OuterGear, type RShape } from "@/r-machine/setup";
export const r = OuterGear.withDeps("base/config") // A BaseGear dependency
.withState({ count: 0 }) // The initial state
.define((plugin, _) => {
const [config, $] = plugin;
return {
count: _.getter(() => $.state.count),
inc: _.action(() => ({ count: $.state.count + config.incValue })),
};
});
export type Outer_Counter = RShape<typeof r>;Plug — the one consumer primitive
Components reach any resource through Plug (or ClientPlug / ServerPlug for SSR; DirectPlug for container-free use outside any framework — workers, cron, scripts, ...). Same call shape for gears, shells, single or many:
// components/my-component.tsx
import { ClientPlug } from "@/r-machine/client-toolset";
import { Button } from "@/components/ui/button";
const plug = ClientPlug("outer/counter", "shell/common");
export default function MyComponent() {
const [counter, shell] = plug.useR();
return (
<div>
<h1>{counter.count}</h1>
<Button onClick={counter.inc}>{shell.addButton}</Button>
</div>
);
}
MyComponent.plug = plug; // attached to the consumer for testing purposes with mockPlugTesting
For tests, mockPlug( ... ).with({ ... }) is the single override primitive — uniform across gears, shells and consumers.
// tests/r-machine/pub/outer/counter.test.ts
import { mockPlug } from "@r-machine/testing";
import { describe, expect, it } from "vitest";
import { r } from "@/r-machine/pub/outer/counter";
describe("outer/counter", () => {
it("starts at 0 and increases", async () => {
using ctrl = mockPlug(r).with({ 0: { incValue: 1 } }); // base/config mocked
const counter = await ctrl.createRes();
expect(counter.count).toBe(0);
counter.inc();
expect(counter.count).toBe(1);
});
});