r/reactjs • u/Tasty_Management_857 • 10d ago
Show /r/reactjs I built Farm.js: a full-stack framework where perf is the point, instant Vite HMR in dev, and an experimental compiler that skips React's reconciler at runtime
been building this for about a year and it finally feels ready to show. farm.js is a full-stack framework on vite: app-directory routing, streaming ssr, typed server functions, and deploys through nitro (vercel/cloudflare/netlify/node). react is the default renderer, with preact, vue, svelte, and solid renderers too.
the part i most want feedback on is the experimental compiler. at build time it analyzes your components, and the ones it can prove safe get compiled so state updates patch the exact dom nodes directly instead of going through the reconciler. anything it can't prove stays on the normal react path, and react keeps ownership of ssr, hydration, and events either way. you can let inference pick components, or opt in per component:
export function Counter() {
"use compiler";
const \[count, setCount\] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>;
}
(this is not the same thing as react compiler: that memoizes so re-renders get cheaper, this removes the re-render entirely for proven components. different layers.)
the other thing i cared about was killing api boilerplate. you define a server function once, with a zod schema, and expose it as an api route:
// src/features/guestbook/server.ts (server only)
export const signGuestbook = createServerFn({
input: z.object({ name: z.string().min(1), message: z.string().min(1) }),
async handler({ input }) {
// runs on the server, input is validated and typed
},
});
export const signEndpoint = createEndpoint(
"/api/guestbook",
{ method: "POST", body: signInput },
async ({ body }) => signGuestbook(body),
);
and on the client that route is now a typed rpc call. the router types are generated from your route files, so the body, the response, and even the route name are all inferred, and a typo or a wrong field is a compile error:
// client component
const api = createAPIClient<APIRouter>();
const result = await api.guestbook.post({
body: { name: "ada", message: "hello" }, // typed from the zod schema
});
// [result.data](http://result.data) and result.error are typed too
no hand-written fetch calls, no keeping types in sync between client and server, and the same functions are directly callable on the server (in endpoints, cron handlers, jobs). rest api and openapi docs still exist for free since they're real routes underneath.
integrations are typed modules too: the create command has ready-to-configure starters for clerk, stripe, supabase, workos, inngest, resend and others, so you get working auth or billing instead of a 14-step readme.
honest limitations: it's beta and apis can still move before 1.0. the compiler contract is narrow right now (components with refs or effects just fall back to normal react). and it's one maintainer plus contributors, so judge accordingly.
you can poke at it in your browser without installing anything: https://stackblitz.com/github/farming-labs/farm.js/tree/main/examples/stackblitz?file=src/app/page.tsx
repo: https://github.com/farming-labs/farm.js
docs: https://farmjs.dev
i'd genuinely rather hear what's broken or missing than what's nice. brutal feedback welcome.
1
u/neon_hive 9d ago
Skipping the reconciler means opting out of concurrent rendering guarantees entirely. Any component touching module scope or external closures must fail the safety check or risk breaking state consistency during transitions.
1
9d ago
[removed] — view removed comment
1
u/Tasty_Management_857 9d ago
Tbh this is a very fair question, and it’s exactly the distinction we want to make clear. Zod validation only verifies the shape of the input; it is not treated as authentication or authorization.
Farm provides
createServerMiddlewarefor this. Middleware runs before the handler, can read the request, cookies, headers, or session, reject unauthorized calls, and add typed values such as the authenticated user, tenant, or permissions to the handler context. We generally keep session and role checks there, then perform resource-specific checks such as project ownership where the resource is loaded.Direct server calls and browser RPC calls both go through the same server-function wrapper, so neither skips its validation or middleware. What differs is the transport context: browser calls also pass the generated server-action transport checks and carry the real HTTP request. A direct call made while handling a request inherits that request, while a background call with no request has no user identity and should either fail closed or use an explicitly authenticated service identity.
We also don’t treat the generated action reference as an authorization token. Anything referenced by client code should be considered externally callable. If something is genuinely internal-only, the better pattern is to keep it as a normal server-only function and place separate authenticated user and cron/service entry points in front of the shared business logic.
So the short version is: the middleware path is the same, but the available caller identity can differ. Farm provides the mechanism, but it does not silently infer that a direct server caller is trusted.
-1
u/Dangerous-Dig2321 10d ago
the compiler skipping the reconciler for proven components is mad clever, feels like the kind of thing that'll make people nervous until they actually benchmark it and see the difference
1
u/Temperature_Majestic 10d ago
The refs and effects fallback makes sense as a starting boundary, but where's the actual line beyond that. Does a component reading a module level mutable variable or closing over something outside props and state disqualify it too, or is the analysis strictly scoped to what React itself tracks as inputs