Skip to content
rshono

A minimal framework for React Server Components

Hono for the server, Rspack for the build, React Server Components for the rendering. One required file, nine exports, and no conventions around them.

Package manager
npx @rshono/create@latest my-app
pnpx @rshono/create@latest my-app
yarn dlx @rshono/create@latest my-app
bunx @rshono/create@latest my-app

Best-in-class ergonomics: write server and client together

A page awaits your database directly, with no API layer in between. A client component reads the same url and params from one hook, and calls a ‘use server’ function like any other function. The boundary is a directive, not a directory.

src/routes.ts

Every page and endpoint in one table, matched in order.

import { defineRoutes } from '@rshono/core';

export const routes = defineRoutes({
  routes: [
    { path: '/', component: () => import('./pages/home') },
    { path: '/posts/:id', component: () => import('./pages/post') },
    { type: 'endpoint', path: '/api/health', server: () => import('./api/health') },
  ],
  notFound: { component: () => import('./pages/404') },
});

Server: a page

A server component, awaiting data.

import type { PageProps } from '@rshono/core';
import { db } from '../db';

export default async function Post({ params, ctx }: PageProps<'/posts/:id'>) {
  const post = await db.getPost(params.id);
  const theme = ctx.cookies.get('theme') ?? 'light';
  return <Article post={post} theme={theme} />;
}

Client: a component

One hook for the URL, the params and the router.

'use client';
import { useNavigation } from '@rshono/core/client';

export function Pager() {
  const { url, router } = useNavigation();
  const page = Number(url.searchParams.get('page') ?? 1);
  return <button onClick={() => router.push(`?page=${page + 1}`)}>Next</button>;
}

Why choose rshono?

Because there is so little of it. Nine exported values across three import paths, five direct dependencies, and no framework-owned replacement for an HTML element you already know. Hono and Rspack stay reachable underneath, unrenamed.

One toolchain, three commands

The same pair of Rspack configs builds dev and production. dev runs the real production server bundle in a worker thread, with HMR that keeps browser state.

rshono dev
rshono build
rshono start

A scaffolder with six questions

Where it goes, deploy target, styling, formatter/linter preset, install, git. Every question is also a flag, a non-interactive terminal implies --yes, and the package manager that ran it is the one the project gets.

  • node
  • cloudflare
  • vercel
  • aws-lambda

One build command per target →

Measured, not claimed

One app built three ways, on Apple M1 · 8 cores. Every metric →

Start with a scaffold

Package manager
npx @rshono/create@latest my-app
pnpx @rshono/create@latest my-app
yarn dlx @rshono/create@latest my-app
bunx @rshono/create@latest my-app