Reference
API reference
Every export of @rshono/core, @rshono/core/server and @rshono/core/client, in one place.
Three entry points, and which one an import comes from tells you where the code runs. That is most of what there is to know about the surface.
| Import | Runs | Holds |
|---|---|---|
@rshono/core |
build time, server | route and config declaration, and the types pages are written against |
@rshono/core/server |
per request, server only | the request context, redirect / notFound, error reporting, publicUrl |
@rshono/core/client |
browser, from 'use client' modules |
the navigation hook and the boundaries |
@rshono/core pulls in no runtime machinery — importing it from server code is free. @rshono/core/server
from a 'use client' module is a mistake: those run in the browser, where no request is bound. Read what
you need on the server and pass it down as props.
These three are the whole public surface — the package's exports map lists only them, so there is no
deeper path to import. Everything else is framework plumbing.
All three are ESM only: the map declares import and types conditions and no require one, so
require('@rshono/core') is ERR_PACKAGE_PATH_NOT_EXPORTED. Use import, or await import() from
CommonJS.
@rshono/core
Functions
| Signature | What it does |
|---|---|
defineRoutes(config): RouteConfig |
Declares the app's route table in src/routes.ts. Also accepts a bare Route[] as shorthand. Cross-checks every page's props against its own path. |
defineConfig(config): RshonoConfig |
Types rshono.config.ts. Identity function — it exists for the autocomplete. |
See Routing and Configuration.
Types
| Type | What it describes |
|---|---|
PageProps<Path, E> |
What every page receives: url, params, ctx. Pass the route's path to type params key-by-key. |
PageComponent<P> |
A page: a server component returning ReactNode or Promise<ReactNode>. |
PathParams<P> |
The params record a path pattern implies — '/users/:id' → { id: string }. PageProps applies it for you. |
PageRoute |
A path rendered by a server component: path, component, and optional render / staticPaths. |
EndpointRoute |
A path served by a Hono handler: type: 'endpoint', path, server, optional method (one or a list). |
EndpointServerModule |
What an endpoint's module must export — a single named handler. |
Route |
PageRoute | EndpointRoute. |
RouteConfig<TRoutes> |
The object defineRoutes takes: routes, plus optional notFound and error. |
FallbackPage |
The notFound / error page shape — a component with no path of its own. |
ErrorPageProps<E> |
PageProps plus error, for the page declared as error. |
ErrorPageInfo |
{ message, stack? }. Redacted in production: a generic message, and stack is undefined. |
HTTPMethod |
'get' | 'post' | 'put' | 'patch' | 'delete' | 'options' | 'all'. A HEAD reaches 'get'. |
RshonoConfig |
Every field of rshono.config.ts. All optional. |
RspackHookContext |
{ isServer, isDev }, handed to the rspack config hook. |
DeployTarget |
'node' | 'cloudflare' | 'vercel' | 'aws-lambda'. |
@rshono/core/server
Server-only, and request-scoped. See Pages and Usage.
Functions
| Signature | What it does |
|---|---|
getRequestContext<E>(): RequestContext<E> |
The current request's context. Memoised per request. Throws at module load, and while prerendering a render: 'static' route. |
redirect(location, status?): never |
Throws a control signal the framework turns into a redirect. status defaults to 303. |
notFound(): never |
Aborts the render with a 404 and the app's not-found page. |
onServerError(handler): void |
Registers one handler for every error the framework catches. Call it once, at the top level of src/server.ts. |
publicUrl(c): URL |
The browser-facing URL for a Hono Context — proxy-corrected under trustProxy. For middleware, which has no request context. |
publicUrl is the one export here meant for src/server.ts rather than a component: middleware is
handed Hono's c and reads c.req.url, the address the server was reached on. Give Hono's own
middleware the browser's origin instead — csrf({ origin: (origin, c) => origin === publicUrl(c).origin }).
In a server component or an action, getRequestContext().url is the same value, cached per request.
redirect and notFound never return, so TypeScript narrows away the code after them and you don't
need to return the call. Don't wrap either in a try/catch that swallows the signal.
Both have to be reached before the page shell is sent. A page streams, so the status line goes out as
soon as the shell is ready and HTTP has no take-backs after that: called from a <Suspense> boundary that
resolves later, neither can still be a 3xx or a 404. The response is committed as 200 text/html, a browser
with JavaScript follows the signal through the payload, and a visitor without it stays on the fallback.
Decide in middleware or in the page component body, above the boundary — rshono dev warns when a signal
arrives too late.
import { getRequestContext, redirect } from '@rshono/core/server';
export default async function Dashboard() {
const session = getRequestContext().cookies.get('session');
if (!session) redirect('/login');
return <Layout>Signed in as {session}</Layout>; // session is defined here
}
RequestContext
What getRequestContext() returns, and the very same object a page gets as its ctx prop. Exported as a type
only — one instance exists per request and application code never constructs it.
Reads are safe anywhere. Writes are not — see Writes happen before the render.
| Member | What it is |
|---|---|
url |
The browser-facing URL, proxy-header aware. Parsed once and cached. |
req |
Hono's HonoRequest — method, headers, query, body readers. |
params |
Matched route params. A page gets these typed as its params prop; this is for everywhere else. |
env |
Process env, merged on a bindings platform with its bindings (which win). See Environment. |
var |
Typed variables a middleware set with c.set(…). |
cookies.get(name) |
One cookie, or undefined. |
cookies.all() |
Every cookie as { name: value }. |
cookies.set(name, value, options?) |
Sets a cookie on the response. Throws in a page. |
cookies.delete(name, options?) |
Clears one. Pass the path/domain it was set with. Throws in a page. |
setHeader(name, value, options?) |
Sets a response header. Throws in a page. |
hono |
The underlying Hono Context — the escape hatch for everything not above. |
What this adds over Hono's own Context is a proxy-aware cached URL, an env that merges a platform's
runtime bindings over process env, cookies without a second import, and writes that tell you when they
cannot work. The long tail — executionCtx.waitUntil() and whatever Hono adds next — is ctx.hono.
Hono's response builders (redirect, notFound, json, text, html, body, status, header)
are present as stubs that throw and name the right API, because a page returns JSX and the framework
builds the response from it — reaching them through ctx.hono bypasses the error without making them
work. They are permanent: the strike-through your editor draws over them comes from a @deprecated tag
put there for the strike-through, not because they are going away.
ctx cannot be handed to a 'use client' component — it wraps the live request. Reading it on a
render: 'static' page throws, because there is no request at build
time.
Writes happen before the render
A page streams. By the time its component runs the response head is already committed — on a soft
navigation the flight response is built before the page's first line executes. A header set from there
would land on a full page load and vanish on a soft navigation, so cookies.set, cookies.delete and
setHeader throw inside a page render rather than work half the time.
Set them from a 'use server' action, which runs before the render:
'use server';
import { getRequestContext, redirect } from '@rshono/core/server';
export async function login(form: FormData) {
const ctx = getRequestContext();
ctx.cookies.set('session', await createSession(form), { httpOnly: true, sameSite: 'Lax', path: '/' });
redirect('/dashboard');
}
Or from middleware, for anything belonging to the page rather than to one mutation — Cache-Control,
X-Robots-Tag. Middleware and { type: 'endpoint' } routes run outside the request context, so
getRequestContext() is not available in them; they are handed Hono's c instead, which is all they
need:
// src/server.ts
server.use('/blog/*', async (c, next) => {
await next();
c.header('cache-control', 'public, max-age=600, s-maxage=3600');
});
| Where | Reads | Writes |
|---|---|---|
| Pages, nested server components | ✅ | ❌ throws |
'use server' actions |
✅ | ✅ ctx.cookies.set(…) / ctx.setHeader(…) |
Middleware in src/server.ts |
via c |
✅ c.header(…) / setCookie(c, …) |
{ type: 'endpoint' } routes |
via c |
✅ same, on the c the handler is passed |
Types
| Type | What it describes |
|---|---|
RequestContext<E> |
The request context above. E is the app's Hono Env, which types var and env. |
EnvVars<E> |
What ctx.env resolves to: Bindings merged with Record<string, string | undefined>. |
RedirectStatus |
301 | 302 | 303 | 307 | 308. |
ServerErrorHandler |
(error, context) => void — what onServerError takes. |
ServerErrorContext |
{ source, request, hono, waitUntil }, the second argument to that handler. |
ServerErrorSource |
'action' | 'render' | 'ssr' | 'request' — which stage produced the error. |
An 'action' error is the one worth wiring up: React sends the client an opaque marker with no message
in production, so a handler is the only place the real error is visible.
hono is the request's Hono context, for hono.var and hono.env — handed over because
getRequestContext() is not reachable from the handler. waitUntil holds a serverless invocation open
until the report has been sent; see error reporting for what it does per
target.
@rshono/core/client
Every export is itself a 'use client' module, so a server component can render AsyncBoundary directly.
The hook needs a client component.
Hook
| Signature | What it returns |
|---|---|
useNavigation(): NavigationState |
{ url, params, router } — the current location, and the router. |
url and params are computed on the server and travel in the flight payload, so they are right during
SSR and update on every navigation. The fragment is the exception: a browser never sends #… to the
server, so url.hash is read from the address bar after hydration and follows in-page links and
Back/Forward with no request either way. In a server component, read the same data from
getRequestContext().
'use client';
import { useNavigation } from '@rshono/core/client';
export function NextPage() {
const { url, router } = useNavigation();
const page = Number(url.searchParams.get('page') ?? '1');
return (
<button disabled={router.pending} onClick={() => router.push(`${url.pathname}?page=${page + 1}`)}>
Next
</button>
);
}
router holds push(href), replace(href), back(), forward(), refresh() and the pending flag.
Every one is a soft navigation: the page's flight payload is fetched and applied in place, so client
state outside the changed subtree survives. Off-site hrefs, and a traversal that leaves the app, fall back
to a full load.
Components
| Component | Props | What it does |
|---|---|---|
<AsyncBoundary> |
loading, error, onError, resetKeys |
A Suspense fallback and an error fallback in one wrapper — the common case for an async section. |
<CatchBoundary> |
fallback, onError, resetKeys |
Error boundary alone. Omit fallback to report and re-throw to the next boundary out. |
loading on AsyncBoundary is required — a loading state is the reason to reach for it over
CatchBoundary, so showing nothing while loading is an explicit loading={null}. error is optional:
omit it and throws propagate to the next boundary out or the global error page. A redirect() is never
absorbed by either boundary — it's navigation, not failure.
AsyncBoundary is scoped to the route: a soft navigation to a different pathname mounts the incoming
route's boundary, so its loading state shows while the new data streams, and a same-page update
(refresh(), a server action, a query-string change) keeps the content it has already revealed. That
scope is a remount — client state and mount effects under the boundary do not survive a route change — so
a section whose state must outlive navigation wants a bare CatchBoundary instead. CatchBoundary is not
scoped — pass resetKeys={[url.pathname]} to clear its error state on navigation.
Types
| Type | What it describes |
|---|---|
NavigationState |
{ url, params, router } — what useNavigation() returns. |
NavigationRouter |
The imperative actions plus pending. |
AsyncBoundaryProps |
Props of <AsyncBoundary>. |
CatchBoundaryProps |
Props of <CatchBoundary>. |
ErrorFallback |
ReactNode, or (error, reset) => ReactNode. The function form only works from a 'use client' module. |