Inspired by remix-express-vite-plugin from @kiliman
This package contains a helper function that enables you to create your Hono
server directly from you entry.server.tsx. Since the Hono server is built along
with the rest of your Remix app, you may import app modules as needed. It also
supports Vite HMR via the react-router-hono-server/dev
plugin (which is required
for this to function).
It relies on remix-hono and presets a default Hono server config that you can customize
Important
Only works with Remix in ESM mode
Only works with Vite
Only works for node
Tip
You can use remix-hono to add cool middleware like session
Install the following npm package. NOTE: This is not a dev dependency, as it creates the Hono server used in production.
npm install react-router-hono-server
From your entry.server.tsx file, export the server from createHonoServer
and
name it server
or the name you defined in devServer({exportName})
in your vite.config.ts.
// app/entry.server.tsx
import { createHonoServer } from "react-router-hono-server/node";
export const server = await createHonoServer();
You can create your server in a separate file and export it from your entry.server.tsx.
It is useful if you have many middleware and want to keep your entry.server.tsx clean.
// app/server.ts
import { createHonoServer } from "react-router-hono-server/node";
export const server = await createHonoServer();
// app/entry.server.tsx
export * from "./server";
// vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import { installGlobals } from "@remix-run/node";
import { devServer } from "react-router-hono-server/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
installGlobals();
export default defineConfig({
build: {
target: "esnext",
},
plugins: [devServer(), remix(), tsconfigPaths()],
});
Important
Change the target
to esnext
in your vite.config.ts file
build: {
target: "esnext",
},
"scripts": {
"build": "NODE_ENV=production remix vite:build",
"dev": "vite --host",
"start": "NODE_ENV=production node ./build/server/index.js"
},
This helper function works differently depending on the environment.
For development
, it creates an Hono server that the Vite plugin will load
via viteDevServer.ssrLoadModule('virtual:remix/server-build')
.
The actual server is controlled by Vite through @hono/vite-dev-server
, and can be configured via vite.config.ts server
options.
For production
, it will create a standard node HTTP server listening at HOST:PORT
.
You can customize the production server port using the port
option of createHonoServer
.
When building for production, the Hono server is compiled in the same bundle as the rest of your Remix app, you can import app modules just like you normally would.
To run the server in production, use NODE_ENV=production node ./build/server/index.js
.
That's all!
export type HonoServerOptions<E extends Env = BlankEnv> = {
/**
* Enable the default logger
*
* Defaults to `true`
*/
defaultLogger?: boolean;
/**
* The port to start the server on
*
* Defaults to `process.env.PORT || 3000`
*/
port?: number;
/**
* The directory where the server build files are located (defined in vite.config)
*
* Defaults to `build/server`
*
* See https://remix.run/docs/en/main/file-conventions/vite-config#builddirectory
*/
buildDirectory?: string;
/**
* The file name of the server build file (defined in vite.config)
*
* Defaults to `index.js`
*
* See https://remix.run/docs/en/main/file-conventions/vite-config#serverbuildfile
*/
serverBuildFile?: `${string}.js`;
/**
* The directory where the assets are located (defined in vite.config, build.assetsDir)
*
* Defaults to `assets`
*
* See https://vitejs.dev/config/build-options#build-assetsdir
*/
assetsDir?: string;
/**
* Customize the Hono server, for example, adding middleware
*
* It is applied after the default middleware and before the remix middleware
*/
configure?: <E extends Env = BlankEnv>(server: Hono<E>) => Promise<void> | void;
/**
* Augment the Remix AppLoadContext
*
* Don't forget to declare the AppLoadContext in your app, next to where you create the Hono server
*
* ```ts
* declare module "@remix-run/node" {
* interface AppLoadContext {
* // Add your custom context here
* }
* }
* ```
*/
getLoadContext?: (
c: Context,
options: Pick<RemixMiddlewareOptions, "build" | "mode">
) => Promise<AppLoadContext> | AppLoadContext;
/**
* Listening listener (production mode only)
*
* It is called when the server is listening
*
* Defaults log the port
*/
listeningListener?: (info: { port: number }) => void;
/**
* Hono constructor options
*
* {@link HonoOptions}
*/
honoOptions?: HonoOptions<E>;
};
You can add additional Hono middleware with the configure
function. If you
do not provide a function, it will create a default Hono server.
The configure
function can be async. So, make sure to await createHonoServer()
.
If you want to set up the Remix AppLoadContext
, pass in a function to getLoadContext
.
Modify the AppLoadContext
interface used in your app.
Since the Hono server is compiled in the same bundle as the rest of your Remix app, you can import app modules just like you normally would.
// app/entry.server.tsx
import { createHonoServer } from "react-router-hono-server/node";
/**
* Declare our loaders and actions context type
*/
declare module "@remix-run/node" {
interface AppLoadContext {
/**
* The app version from the build assets
*/
readonly appVersion: string;
}
}
export const server = await createHonoServer({
getLoadContext(_, { build, mode }) {
const isProductionMode = mode === "production";
return {
appVersion: isProductionMode ? build.assets.version : "dev",
};
},
});
// app/routes/test.tsx
export async function loader({ context }: LoaderFunctionArgs) {
// get the context provided from `getLoadContext`
return { appVersion: context.appVersion }
}
Middleware are functions that are called before Remix calls your loader/action.
Hono is the perfect tool for this, as it supports middleware out of the box.
See the Hono docs for more information.
You can imagine many use cases for middleware, such as authentication, protecting routes, caching, logging, etc.
See how Shelf.nu uses them!
Tip
This lib exports one middleware cache
(react-router-hono-server/middleware
) that you can use to cache your responses.
It is easy to use remix-hono middleware with this package.
import { createCookieSessionStorage } from "@remix-run/node";
import { createHonoServer } from "react-router-hono-server/node";
import { session } from "remix-hono/session";
export const server = await createHonoServer({
configure: (server) => {
server.use(
session({
autoCommit: true,
createSessionStorage() {
const sessionStorage = createCookieSessionStorage({
cookie: {
name: "session",
httpOnly: true,
path: "/",
sameSite: "lax",
secrets: [process.env.SESSION_SECRET],
secure: process.env.NODE_ENV === "production",
},
});
return {
...sessionStorage,
// If a user doesn't come back to the app within 30 days, their session will be deleted.
async commitSession(session) {
return sessionStorage.commitSession(session, {
maxAge: 60 * 60 * 24 * 30, // 30 days
});
},
};
},
})
);
},
});
You can create middleware using the createMiddleware
or createFactory
functions from hono/factory
.
Then, use them with the configure
function of createHonoServer
.
import { createMiddleware } from "hono/factory";
import { createHonoServer } from "react-router-hono-server/node";
export const server = await createHonoServer({
configure: (server) => {
server.use(
createMiddleware(async (c, next) => {
console.log("middleware");
return next();
})
);
},
});
This project follows the all-contributors specification. Contributions of any kind welcome!