remix-hono: running React Router on a Hono server
Hono middlewares for Remix
At a glance
- What is it?
- remix-hono is the adapter that lets a React Router or Remix application run on a Hono server, with Hono owning the HTTP layer and its middleware chain and React Router owning the routing and data loading. The interesting parts are the session middleware, which resolves a React Router session secret from the Hono context, and the Cloudflare-specific pieces for static assets and i18next.
- Who is it for?
- remix-hono suits a team that wants Hono's middleware ecosystem and its edge runtime behaviour while keeping React Router's routing and loader model, since the adapter is a thin bridge rather than a port. It does not suit you if you only need one of the pieces, because the session and Cloudflare helpers are usable on their own, and it does not suit you if you want everything to arrive in one install, since the Cloudflare, i18next and zod peers are deliberately not bundled.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 101 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two frameworks, one request pipeline
The adapter's whole argument fits in two sentences from the README. React Router is a web framework for building web applications which can run on the Edge, and Hono is a small and ultrafast web framework for the Edges. The adapter lets you use Hono with React Router so you can use the best of each one: let Hono power your HTTP server and its middlewares, then use React Router to build your web application.
That division is the design. Hono owns the transport and the middleware chain, React Router owns the routing and the data loading, and the package is the piece that joins them.
The join is a single Hono middleware. You mount it with server.use on the wildcard path, passing the build output and the mode, and then export the request handler for the platform. The shape of it, in full, is this:
import { logDevReady } from "@react-router/cloudflare";
import { Hono } from "hono";
// You can also use it with other runtimes
import { handle } from "hono/cloudflare-pages";
import { reactRouter } from "remix-hono/handler";
import build from "./build/server";
if (process.env.NODE_ENV === "development") logDevReady(build);
/* type your Cloudflare bindings here */
type Bindings = {};
/* type your Hono variables (used with c.get/c.set) here */
type Variables = {};
type ContextEnv = { Bindings: Bindings; Variables: Variables };
const server = new Hono<ContextEnv>();
// Add the React Router middleware to your Hono server
server.use(
"*",
reactRouter({
build,
mode: process.env.NODE_ENV as "development" | "production",
// getLoadContext is optional, the default function is the same as here
getLoadContext(c) {
return c.env;
},
}),
);Two details in that block carry most of the weight. The build import comes from the server build directory, so the React Router build has to exist before the server module is loaded. And getLoadContext is where the two frameworks exchange state, with the default implementation returning the Hono environment, so the Cloudflare bindings are visible to loaders and actions.
The order of the middleware matters and the documentation says so explicitly in the later examples: the React Router request handler has to be the last one. Anything registered after it never runs, because the React Router middleware terminates the chain by producing the response.
The package is on npm as remix-hono, MIT licensed, and the project is at version 0.0.18 with the last push on 2026-07-14. The tags on the repository are cloudflare, cloudflare-pages, hono, remix and remix-run.
Optional peers, and why they are not bundled
The installation step is one command:
npm add remix-honoAnd then the part that trips people up. Three packages are optional dependencies, and you install them yourself depending on which features you use.
The @react-router/cloudflare package is needed if you are using the Cloudflare integration. i18next and remix-i18next are needed if you are using the i18n middleware. And zod is needed if you are using typedEnv.
The documentation is direct about the reason: you do not really need to install them if you do not use them, but you will need to install them yourself, because they do not come automatically, if you use the features that depend on those packages.
That is a deliberate choice rather than an oversight. The Cloudflare package, the i18n stack and a schema validator are three quite different concerns, and pulling all of them into every install of a middleware adapter would mean every consumer carries the transitive dependencies of features they never touch. Leaving them as peers that the adapter does not import keeps the base install small.
The cost is that a mistake surfaces at runtime rather than at install time. If you import the i18n middleware without i18next installed, the failure is a module resolution error when the server starts, not a warning during npm install. The README mitigates this by listing the pairing next to each feature.
The package manifest confirms the approach. The exports map lists seven entry points, ./cloudflare, ./handler, ./i18next, ./security, ./session, ./trailing-slash and ./typed-env, and the peer dependency block starts with the Cloudflare package at version 8, so the optional pieces are wired at the package level rather than being left to chance.
Sessions, and the problem they solve
The session middleware is the part of the package that does the most interesting work, and the reason is stated plainly: React Router sessions typically use a secret coming from the environment, so you need access to the Hono c.env to use them. If you are using the Worker KV session storage you also need to pass the KV binding to the middleware.
That is the crux of the whole adapter. React Router's session model is built on a secret that lives in the environment, and the environment on Cloudflare is not process.env, it is the request context. Without a bridge, the session middleware has nowhere to read the secret from.
The generic session middleware takes a createSessionStorage factory that receives the context, which is how it gets access to both:
import { session } from "remix-hono/session";
import { createWorkerKVSessionStorage } from "@react-router/cloudflare";
server.use(
"*",
session({
autoCommit: true,
createSessionStorage(c) {
return createWorkersKVSessionStorage({
kv: c.env.MY_KV_BINDING,
cookie: {
name: "session",
httpOnly: true,
secrets: [c.SESSION_SECRET],
},
});
},
}),
);The cookie options show the shape of it. The name is session, httpOnly is true, and secrets is an array holding the session secret, which the middleware resolves from the context. The factory is where you call createWorkersKVSessionStorage with the KV binding and the cookie configuration.
The session middleware itself is generic and works with any storage mechanism, and there are two Cloudflare-specific shorthands for the two common cases. The workerKVSession middleware wraps the KV storage, and the cookieSession middleware wraps the cookie storage. Both take the same autoCommit option and the same cookie options, and in both the secrets function receives the context so it can reach the secret.
There is one behavioural detail worth noting. The session object is only defined if autoCommit was set to true in the middleware options; if you set it to false, you have to call sessionStorage.getSession() manually. That is because autoCommit is what determines whether the middleware prepares the session for you at request time.
The helpers getSessionStorage and getSession, both imported from remix-hono/session, are then used inside getLoadContext to attach the storage and the session to the context that loaders and actions receive.
Static assets and internationalisation, two more middlewares
The Cloudflare-specific part of the package has two further pieces.
The first is static assets. If you are using Remix Hono with Cloudflare you need to serve your static files from the public folder, except for public/build, and the staticAssets middleware does exactly that:
import { staticAssets } from "remix-hono/cloudflare";
import { reactRouter } from "remix-hono/handler";
server.use(
"*",
staticAssets(),
// Add React Router request handler as the last middleware
reactRouter(options),
);The exception for public/build is the detail that matters. React Router's own build output is handled by the React Router middleware, so letting the static middleware also serve that directory would mean two handlers racing for the same files. The README also notes you need @react-router/cloudflare installed for this, which it installs with npm add @react-router/cloudflare.
The second is i18next. If you are using remix-i18next to support internationalisation in your React Router app, the i18next middleware sets it up as a middleware that you can then use in your getLoadContext function to pass the locale and t functions to your loaders and actions. It takes the same options as remix-i18next:
import { i18next } from "remix-hono/i18next";
// Same options as in remix-i18next
server.use("*", i18next(options));The helpers i18next.getLocale and i18next.getFixedT are what you use to reach those two values from inside getLoadContext.
Both of these are the same shape as the session work, and that is the pattern across the package. Each middleware's job is to make something that lives in the Hono context available to React Router, either by preparing it in the context or by exposing helpers to read it. The context is the meeting point, and the middleware is the translation layer.
The export map also lists ./security and ./trailing-slash entry points, so the package covers request security helpers and trailing slash handling in the same way, though the README section available here does not describe them in detail.
The repository, and the tooling choices in it
The project is small and the top level says so. There is a .github/ directory, a .gitignore, the LICENSE, the README, a biome.json, a bun.lockb and a bunfig.toml, a package.json, a scripts/ directory, a src/ directory, a test/ directory, a tsconfig.json and a typedoc.json.
Two of those choices are worth noting. The lockfile is bun.lockb, so the package is developed and tested with bun rather than npm or pnpm, and the bunfig.toml alongside it holds the bun configuration. And the linting and formatting is done with Biome, configured in biome.json, with the scripts named quality and quality:fix where the latter runs the check with both the write and unsafe flags.
The build is a plain tsc, and typecheck is tsc with noEmit. The exports script runs bun run against a script in the scripts/ directory, which is how the export map is kept in step with the source tree. The typedoc.json is the configuration for the API reference, and the homepage points at the generated documentation.
The package manifest is also explicit about what it publishes. The files array contains src, build, package.json and README.md, so the source is shipped alongside the compiled output rather than only the build. The type field is module, so it is an ES module package, and sideEffects is set to false, which tells a bundler it can drop unused imports, which matters for a package whose whole purpose is to be imported into someone else's server.
The author is Sergio Xalambrí, with funding and sponsorship pointing at his GitHub sponsors page. The keywords list react-router alongside remix and remix-run, which is the clearest sign that the package has moved with the framework's own rename, and the last three releases, 0.0.16 in March 2024, 0.0.17 in December 2024 and 0.0.18 later the same month, are consistent with a package that is maintained in small increments rather than in feature releases.
Editorial conclusion
remix-hono suits a team that wants Hono's middleware ecosystem and its edge runtime behaviour while keeping React Router's routing and loader model, since the adapter is a thin bridge rather than a port. It does not suit you if you only need one of the pieces, because the session and Cloudflare helpers are usable on their own, and it does not suit you if you want everything to arrive in one install, since the Cloudflare, i18next and zod peers are deliberately not bundled. Before you adopt it, read the middleware ordering rule, because registering the React Router handler anywhere but last is the mistake that produces a server which appears to work and never reaches your own middleware.
Frequently asked questions
How do I install remix-hono and its optional dependencies?
Run npm add remix-hono for the package itself. Then install the optional peers for the features you use: @react-router/cloudflare for the Cloudflare integration, i18next and remix-i18next for the i18n middleware, and zod for typedEnv. They are not installed automatically and do not come with the package.
Why does the React Router middleware have to be registered last?
Because it produces the response and ends the chain, so anything registered after it never runs. In the examples the comment is Ensure React Router request handler is the last one, and Hono middlewares such as basic auth are registered before it.
How do sessions work with remix-hono on Cloudflare?
React Router sessions need a secret from the environment, and on Cloudflare the environment is the Hono context, so the middleware takes a createSessionStorage factory that receives the context and can read c.env for the secret and the KV binding. The workerKVSession and cookieSession middlewares wrap the two common cases, and getSession and getSessionStorage are used in getLoadContext to pass them to loaders and actions.
What does the staticAssets middleware do and why is public/build excluded?
It serves your static files from the public folder when using Remix Hono with Cloudflare, except for public/build. That directory is React Router's own build output, which the React Router middleware already handles, so excluding it stops two handlers from competing for the same files.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sergiodxa-remix-hono)Was this useful?