A weekend coding desk at night with a laptop and a gray-blue cat watching from the windowsill

Twofold: The RSC Framework Built for Weekend Projects

The Gray Cat
The Gray Cat
0 views

Most React frameworks aim straight at production at scale: edge runtimes, caching layers, plugin ecosystems, and a vendor behind the wheel. Twofold goes the other direction. Its one-line pitch is refreshingly honest: it is "a framework for building weekend projects with React Server Components, Tailwind, and TypeScript." Created by Ryan Toronto, @twofold/framework is an RSC-native framework that bakes in a single happy path so you can go from idea to running app without spending Saturday morning configuring tooling.

That framing matters. Twofold is not trying to dethrone Next.js or out-feature React Router. It is built for hobby apps, prototypes, throwaway experiments, and — maybe most usefully — learning how React Server Components actually work. Because it leans directly on bare React 19 primitives like Suspense, Transitions, and Server Functions, the whole thing doubles as a readable reference implementation of "how an RSC framework is put together." This article walks through what it offers, how to get started, and where it shines (and where it does not).

A quick note on maturity before you get excited: Twofold is still on 0.0.x, maintained by one person, and has very modest adoption. Treat it as experimental. It is a wonderful tool for tinkering and learning, not something to bet a paying customer's product on just yet.

Why Another React Framework

Twofold earns its place by being RSC-native from day one. Several of its design choices flow from that decision:

  • RSC-first architecture. React Server Components are the foundation, not an add-on layer. There is no legacy non-RSC mode to support, so the mental model stays consistent across the whole app.
  • A router built on Suspense and Transitions. Navigation uses React Transitions so the UI stays responsive while server components stream in. You get streaming and pending states essentially for free.
  • Server Functions with automatic encryption. Values captured in a server function's closure are automatically encrypted and decrypted, so server-only data bound into an action never leaks into the client bundle.
  • Zero-config tooling. Tailwind CSS, TypeScript, Prettier, the React Compiler, HMR, and linting are all pre-wired. There is nothing to install or configure to get a modern setup.
  • Built on bare React 19 APIs. Rather than hiding everything behind a heavy proprietary abstraction, Twofold stays close to the React primitives, which keeps the codebase legible.

If you have ever wanted to understand RSC without wading through a massive framework, this combination is the selling point.

Getting It Running

Twofold needs a recent Node.js (the current docs ask for Node.js 22.20.0 or newer), so check your version first. The fastest way to start is the scaffolding CLI, which creates a directory, installs dependencies, and initializes a git repository for you.

# npm
npx create-twofold-app@latest

# pnpm
pnpm create twofold-app@latest

The installer prompts for an app name and sets everything up. Once it finishes, start the dev server:

cd my-app
pnpm dev

Open http://localhost:3000 and you have a streaming RSC app running with Tailwind, TypeScript, and the React Compiler already wired in. No tailwind.config archaeology, no Babel babysitting — it just works out of the box.

Building Pages the RSC Way

Twofold uses file-based routing with pages and layouts, so the mental model will feel familiar if you have used the Next.js App Router or Remix. By default, components are Server Components: they render on the server, can fetch data directly, and stream their output to the browser.

// app/pages/index.tsx
async function getPosts() {
  // This runs on the server. You can hit a database or API directly.
  const res = await fetch("https://example.com/api/posts");
  return res.json() as Promise<{ id: string; title: string }[]>;
}

export default async function HomePage() {
  const posts = await getPosts();

  return (
    <main className="mx-auto max-w-2xl p-8">
      <h1 className="text-2xl font-bold">Latest posts</h1>
      <ul className="mt-4 space-y-2">
        {posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </main>
  );
}

Because this is a Server Component, the getPosts call never ships to the client and the data fetching happens before the markup streams down. There is no useEffect, no loading spinner boilerplate, and no client-side fetch waterfall. The Tailwind classes work immediately thanks to the zero-config setup.

When you need interactivity, drop a "use client" boundary. Twofold fully bundles and code-splits both server and client components, so you can freely mix the two.

// app/components/counter.tsx
"use client";

import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button
      className="rounded bg-black px-3 py-1 text-white"
      onClick={() => setCount((c) => c + 1)}
    >
      Clicked {count} times
    </button>
  );
}

Navigating Without the Jank

Navigation in Twofold runs through its Suspense- and Transition-based router. For client components, the framework exposes a small set of first-party modules under @twofold/framework/.... The <Link> component handles client-side navigation, and the useRouter hook gives you imperative control such as refreshing the current route.

"use client";

import { useRouter } from "@twofold/framework/use-router";

export function RefreshButton() {
  const router = useRouter();
  return (
    <button
      className="rounded border px-3 py-1"
      onClick={() => router.refresh()}
    >
      Refresh
    </button>
  );
}

Calling router.refresh() re-runs the server components for the current route and streams the fresh output back in — handy after a mutation when you want the page to reflect new data without a full reload.

Because navigations are wrapped in React Transitions, the previous page stays interactive while the next one loads. Twofold leans into this with the useOptimisticRoute hook, which lets a client component know whether the router is currently transitioning and which page is on its way. That makes loading indicators and optimistic nav states trivial.

"use client";

import { useOptimisticRoute } from "@twofold/framework/use-optimistic-route";

export function NavSpinner() {
  const { isPending } = useOptimisticRoute();
  return isPending ? <span className="animate-pulse">Loading…</span> : null;
}

Server Functions and Secure Closures

Twofold supports React's Server Functions via the "use server" directive, letting client components call server-side code as if it were a local async function. The standout detail here is security: Twofold automatically encrypts and decrypts the variables captured in a server function's closure. If your action closes over a secret, a token, or any server-only value, that data is never serialized into the client bundle — a hardening step that not every RSC framework gives you by default.

// app/actions.ts
"use server";

import { redirect } from "@twofold/framework/redirect";

export async function createPost(formData: FormData) {
  const title = formData.get("title");
  // ...persist to your database here...
  redirect("/");
}
// app/components/new-post-form.tsx
import { createPost } from "../actions";

export function NewPostForm() {
  return (
    <form action={createPost} className="space-y-2">
      <input name="title" className="rounded border px-2 py-1" />
      <button className="rounded bg-black px-3 py-1 text-white">Save</button>
    </form>
  );
}

The @twofold/framework/redirect helper performs a server-side redirect after the action runs, and there is a matching @twofold/framework/not-found for triggering 404 handling and @twofold/framework/cookies for reading and writing cookies.

Flash Messages That Just Work

One genuinely nice touch is the built-in flash messages system, exported from @twofold/framework/flash. Flash messages are type-safe and work in both server and client actions, created via a flash function from inside a server action. They are perfect for the "Post saved!" toast you want to show after a redirect.

// app/actions.ts
"use server";

import { flash } from "@twofold/framework/flash";
import { redirect } from "@twofold/framework/redirect";

export async function savePost(formData: FormData) {
  // ...save the post...
  flash({ type: "success", message: "Post saved!" });
  redirect("/");
}

You then read those messages wherever you render notifications, and the type safety means the shape of each message is checked at compile time. One caveat worth knowing: flash message ordering does not currently survive a page reload, so do not rely on it for anything order-sensitive across full reloads.

Shipping It Somewhere

When your weekend project is ready to share, Twofold's docs cover deployment with Docker and the DigitalOcean App Platform, alongside general guidance for Node-hosted setups. A bonus DX feature: RSC errors render differently in development versus production automatically, with no configuration — verbose and helpful while you are building, clean and safe once you deploy.

Edge platforms are rougher around the edges. There are known Cloudflare compression issues that need workarounds, and Windows route handling is not fully verified. Static file and middleware handling are still being refined. None of this is surprising for a 0.0.x project, but it is worth planning around if your host of choice is on that list.

When To Reach For Twofold (And When Not To)

Twofold sits in the small-but-growing club of minimal, RSC-native frameworks, with Waku by Daishi Kato being its closest philosophical neighbor. Compared to the heavyweights, the trade-offs are clear. Next.js and React Router/Remix are mature, production-grade, and backed by large ecosystems; Next.js bolted RSC onto an existing framework via the App Router, and Remix added RSC support only recently. Twofold, by contrast, was RSC-native from its first versions, has no legacy mode, and bakes in one opinionated happy path with Tailwind, TypeScript, Prettier, the React Compiler, and linting.

The honest summary: reach for Twofold when you want to spin up a side project fast, when you want to learn how RSC frameworks work from a readable codebase, or when you want a zero-config modern React setup for a prototype. The framework's own docs even include a "reasons not to use Twofold" section, which tells you a lot about the spirit of the project. For anything mission-critical, a larger company's product, or a team that needs long-term ecosystem support, the mature options remain the right call.

That self-awareness is exactly what makes Twofold likable. It knows what it is, it does that one thing cleanly, and it invites you to read its source while you build. For a weekend, that is a pretty great deal.