H
Hono4mo ago
predaytor

How to setup Hono with Remix/Cloudflare for Vite dev server?

From the documentation for Cloudflare Pages, there is an adapter and a plugin, but it is not clear how we can configure both Hono and Remix to run on the Vite dev server.
import devServer from '@hono/vite-dev-server'
import adapter from '@hono/vite-dev-server/cloudflare'
import build from '@hono/vite-cloudflare-pages'
import { defineConfig } from 'vite'

export default defineConfig({
plugins: [
devServer({
entry: 'src/index.tsx',
adapter, // Cloudflare Adapter
}),
build(),
],
})
import devServer from '@hono/vite-dev-server'
import adapter from '@hono/vite-dev-server/cloudflare'
import build from '@hono/vite-cloudflare-pages'
import { defineConfig } from 'vite'

export default defineConfig({
plugins: [
devServer({
entry: 'src/index.tsx',
adapter, // Cloudflare Adapter
}),
build(),
],
})
3 Replies
predaytor
predaytor4mo ago
Currently, to run Remix with Cloudflare bindings locally, we rely on the cloudflareDevProxyVitePlugin plugin, which allows us to emulate getLoadContext in dev mode, but we can't seem to integrate Hono entrypoint here: /server/index.ts:
import { logDevReady } from '@remix-run/cloudflare';
import { createPagesFunctionHandler } from '@remix-run/cloudflare-pages';
import { Hono } from 'hono';
import type { EventContext } from 'hono/cloudflare-pages';
import { csrf } from 'hono/csrf';
import { logger } from 'hono/logger';
import { prettyJSON } from 'hono/pretty-json';
import { secureHeaders } from 'hono/secure-headers';
import { staticAssets } from 'remix-hono/cloudflare';
import { trailingSlash } from 'remix-hono/trailing-slash';

import * as build from '../build/server';
import { getLoadContext } from './load-context';

if (process.env.NODE_ENV === 'development') logDevReady(build);

export type ContextEnv = { Bindings: Required<Env> & { eventContext: EventContext } };

const app = new Hono<ContextEnv>({ strict: true });

app.use(csrf());
app.use(logger());
app.use(prettyJSON({ space: 4 }));
app.use(secureHeaders());
app.use(trailingSlash());
app.use(staticAssets({ cache: { public: true, maxAge: '1y', immutable: true } }));

app.use(async c => await createPagesFunctionHandler({ build, getLoadContext })(c.env.eventContext));

export default app;
import { logDevReady } from '@remix-run/cloudflare';
import { createPagesFunctionHandler } from '@remix-run/cloudflare-pages';
import { Hono } from 'hono';
import type { EventContext } from 'hono/cloudflare-pages';
import { csrf } from 'hono/csrf';
import { logger } from 'hono/logger';
import { prettyJSON } from 'hono/pretty-json';
import { secureHeaders } from 'hono/secure-headers';
import { staticAssets } from 'remix-hono/cloudflare';
import { trailingSlash } from 'remix-hono/trailing-slash';

import * as build from '../build/server';
import { getLoadContext } from './load-context';

if (process.env.NODE_ENV === 'development') logDevReady(build);

export type ContextEnv = { Bindings: Required<Env> & { eventContext: EventContext } };

const app = new Hono<ContextEnv>({ strict: true });

app.use(csrf());
app.use(logger());
app.use(prettyJSON({ space: 4 }));
app.use(secureHeaders());
app.use(trailingSlash());
app.use(staticAssets({ cache: { public: true, maxAge: '1y', immutable: true } }));

app.use(async c => await createPagesFunctionHandler({ build, getLoadContext })(c.env.eventContext));

export default app;
/functions/[[path]].ts:
import { handle } from 'hono/cloudflare-pages';

import server from '../server/index';

export const onRequest = handle(server);
import { handle } from 'hono/cloudflare-pages';

import server from '../server/index';

export const onRequest = handle(server);
vite.config.ts:
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
import { getLoadContext } from './server/load-context';

export default defineConfig({
plugins: [remixCloudflareDevProxy({ getLoadContext }), remix()],
});
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
import { getLoadContext } from './server/load-context';

export default defineConfig({
plugins: [remixCloudflareDevProxy({ getLoadContext }), remix()],
});
/server/load-context.ts:
import { type PlatformProxy } from 'wrangler';

// When using `wrangler.toml` to configure bindings,
// `wrangler types` will generate types for those bindings
// into the global `Env` interface.

type Cloudflare = Omit<PlatformProxy<Env>, 'dispose'>;

export type GetLoadContextArgs = { request: Request; context: { cloudflare: Cloudflare } };

export function getLoadContext({ context }: GetLoadContextArgs) {
// ... db, auth stuff etc.

return {
...context,
};
}

declare module '@remix-run/cloudflare' {
interface AppLoadContext extends ReturnType<typeof getLoadContext> {}
}
import { type PlatformProxy } from 'wrangler';

// When using `wrangler.toml` to configure bindings,
// `wrangler types` will generate types for those bindings
// into the global `Env` interface.

type Cloudflare = Omit<PlatformProxy<Env>, 'dispose'>;

export type GetLoadContextArgs = { request: Request; context: { cloudflare: Cloudflare } };

export function getLoadContext({ context }: GetLoadContextArgs) {
// ... db, auth stuff etc.

return {
...context,
};
}

declare module '@remix-run/cloudflare' {
interface AppLoadContext extends ReturnType<typeof getLoadContext> {}
}
maybe we can get rid of the cloudflareDevProxyVitePlugin and configure the Vite dev server properly?
makaron pelnoziarnisty
@predaytor Hi, I have a similar problem. Have you managed to make it work?
predaytor
predaytor2mo ago
@makaron pelnoziarnisty yeah. here is a repo from the creator of hono (https://github.com/yusukebe/hono-and-remix-on-vite). for my setup I did something very similar but integrated a few middleware (eg. d1 database): /server/factory.ts:
import { createFactory } from 'hono/factory';
import type { ContextEnv } from 'server';

export const factory = createFactory<ContextEnv>();
import { createFactory } from 'hono/factory';
import type { ContextEnv } from 'server';

export const factory = createFactory<ContextEnv>();
/server/index.ts:
import { cors } from 'hono/cors';
import { csrf } from 'hono/csrf';
import { showRoutes } from 'hono/dev';
import { logger } from 'hono/logger';
import { prettyJSON } from 'hono/pretty-json';
import { secureHeaders } from 'hono/secure-headers';
import type { Session, User } from 'lucia';
import { staticAssets } from 'remix-hono/cloudflare';
import { trailingSlash } from 'remix-hono/trailing-slash';
import { factory } from 'server/factory';

import { auth } from '~/middleware/auth';
import { db } from '~/middleware/db';
import { remix } from '~/middleware/remix';
import { validateRequest } from '~/middleware/validate-request';

export type ContextEnv = {
Bindings: Required<Env>;
Variables: {
user: User | null;
session: Session | null;
};
};

const app = factory.createApp();

app.use(csrf());
app.use(logger());
app.use(prettyJSON({ space: 4 }));
app.use(secureHeaders());
app.use(trailingSlash());

app.use(db);
app.use(auth);
app.use(validateRequest);

app.use('/api/*', cors());

app.use(
async (c, next) => {
if (process.env.NODE_ENV !== 'development') {
return staticAssets()(c, next);
}
await next();
},

remix,
);

showRoutes(app);

export default app;
import { cors } from 'hono/cors';
import { csrf } from 'hono/csrf';
import { showRoutes } from 'hono/dev';
import { logger } from 'hono/logger';
import { prettyJSON } from 'hono/pretty-json';
import { secureHeaders } from 'hono/secure-headers';
import type { Session, User } from 'lucia';
import { staticAssets } from 'remix-hono/cloudflare';
import { trailingSlash } from 'remix-hono/trailing-slash';
import { factory } from 'server/factory';

import { auth } from '~/middleware/auth';
import { db } from '~/middleware/db';
import { remix } from '~/middleware/remix';
import { validateRequest } from '~/middleware/validate-request';

export type ContextEnv = {
Bindings: Required<Env>;
Variables: {
user: User | null;
session: Session | null;
};
};

const app = factory.createApp();

app.use(csrf());
app.use(logger());
app.use(prettyJSON({ space: 4 }));
app.use(secureHeaders());
app.use(trailingSlash());

app.use(db);
app.use(auth);
app.use(validateRequest);

app.use('/api/*', cors());

app.use(
async (c, next) => {
if (process.env.NODE_ENV !== 'development') {
return staticAssets()(c, next);
}
await next();
},

remix,
);

showRoutes(app);

export default app;
/server/load-context.ts:
import type { Context } from 'hono';
import type { ContextEnv } from 'server';
import { type PlatformProxy } from 'wrangler';

// When using `wrangler.toml` to configure bindings,
// `wrangler types` will generate types for those bindings
// into the global `Env` interface.

type Cloudflare = Omit<PlatformProxy<Env>, 'dispose'>;

export function getLoadContext(c: Context<ContextEnv>) {
const cloudflare = { env: c.env } as Cloudflare;

return {
cloudflare,
auth: c.get('auth'),
db: c.get('db'),
user: c.get('user'),
session: c.get('session'),
};
}

declare module '@remix-run/cloudflare' {
interface AppLoadContext extends ReturnType<typeof getLoadContext> {}
}
import type { Context } from 'hono';
import type { ContextEnv } from 'server';
import { type PlatformProxy } from 'wrangler';

// When using `wrangler.toml` to configure bindings,
// `wrangler types` will generate types for those bindings
// into the global `Env` interface.

type Cloudflare = Omit<PlatformProxy<Env>, 'dispose'>;

export function getLoadContext(c: Context<ContextEnv>) {
const cloudflare = { env: c.env } as Cloudflare;

return {
cloudflare,
auth: c.get('auth'),
db: c.get('db'),
user: c.get('user'),
session: c.get('session'),
};
}

declare module '@remix-run/cloudflare' {
interface AppLoadContext extends ReturnType<typeof getLoadContext> {}
}
/functions/[[path]].ts:
import { handle } from 'hono/cloudflare-pages';

import server from '../server';

export const onRequest = handle(server);
import { handle } from 'hono/cloudflare-pages';

import server from '../server';

export const onRequest = handle(server);
/app/middleware/remix.ts:
import { type ServerBuild, createRequestHandler } from '@remix-run/cloudflare';
import { factory } from 'server/factory';
import { getLoadContext } from 'server/load-context';

export const remix = factory.createMiddleware(async c => {
const build = (await (process.env.NODE_ENV === 'development'
? import('virtual:remix/server-build' as never)
: import('build/server' as never))) as ServerBuild;

const requestHandler = createRequestHandler(build, process.env.NODE_ENV);

return await requestHandler(c.req.raw, getLoadContext(c));
});
import { type ServerBuild, createRequestHandler } from '@remix-run/cloudflare';
import { factory } from 'server/factory';
import { getLoadContext } from 'server/load-context';

export const remix = factory.createMiddleware(async c => {
const build = (await (process.env.NODE_ENV === 'development'
? import('virtual:remix/server-build' as never)
: import('build/server' as never))) as ServerBuild;

const requestHandler = createRequestHandler(build, process.env.NODE_ENV);

return await requestHandler(c.req.raw, getLoadContext(c));
});
/app/middleware/db.ts:
import { drizzle } from 'drizzle-orm/d1';
import type { Context } from 'hono';
import type { ContextEnv } from 'server';
import { factory } from 'server/factory';

import * as schema from '~/db/schema.server';

export const db = factory.createMiddleware(async (c, next) => {
const db = initializeDatabase(c);
c.set('db', db);
return await next();
});

declare module 'hono' {
interface ContextVariableMap {
db: DB;
}
}

function initializeDatabase(c: Context<ContextEnv>) {
return drizzle(c.env.DB, { schema });
}

export type DB = ReturnType<typeof initializeDatabase>;
import { drizzle } from 'drizzle-orm/d1';
import type { Context } from 'hono';
import type { ContextEnv } from 'server';
import { factory } from 'server/factory';

import * as schema from '~/db/schema.server';

export const db = factory.createMiddleware(async (c, next) => {
const db = initializeDatabase(c);
c.set('db', db);
return await next();
});

declare module 'hono' {
interface ContextVariableMap {
db: DB;
}
}

function initializeDatabase(c: Context<ContextEnv>) {
return drizzle(c.env.DB, { schema });
}

export type DB = ReturnType<typeof initializeDatabase>;