Before anything, go ahead and install Bun:
curl -fsSL https://bun.sh/install | bash
Note: I haven’t really even benchmarked the performance of Bun’s HTTP server vs. remix-server
(with Node.js) but Bun’s dependency management is just so fast I couldn’t resist.
Using Bun for dependencies
As simple as running:
bun install
And in most cases it will migrate over your package-lock.json
to a bun.lockb
file.
Using Bun as the runtime
If you’ve been using remix-serve
(like me), you probably don’t have a server.ts
file. Go ahead and create one:
import { createRequestHandler, logDevReady } from "@remix-run/server-runtime";
import { broadcastDevReady, type ServerBuild } from "@remix-run/node";
import * as build from "./build/server/index.js";
import { join } from "path";
const remix_build = build as unknown as ServerBuild;
const handler = createRequestHandler(remix_build, process.env.NODE_ENV);
const port = process.env.PORT || 3000;
console.log(`🚀 Server starting on port ${port}`);
if (process.env.NODE_ENV === "development") {
broadcastDevReady(remix_build);
logDevReady(remix_build);
}
Bun.serve({
port: port,
async fetch(request: Request) {
try {
const url = new URL(request.url);
// Try serving static files from public directory
let file = Bun.file(join("public", url.pathname));
if (await file.exists()) {
const headers = new Headers();
headers.set("Cache-Control", "public, max-age=31536000, immutable");
if (url.pathname.endsWith(".js")) {
headers.set("Content-Type", "application/javascript");
} else if (url.pathname.endsWith(".css")) {
headers.set("Content-Type", "text/css");
} else if (url.pathname.endsWith(".html")) {
headers.set("Content-Type", "text/html");
}
return new Response(file, { headers });
}
// Handle Vite's build output assets
if (url.pathname.startsWith("/assets/")) {
// Try client build directory
file = Bun.file(join("build/client", url.pathname));
if (await file.exists()) {
const headers = new Headers();
if (url.pathname.endsWith(".js")) {
headers.set("Content-Type", "application/javascript");
} else if (url.pathname.endsWith(".css")) {
headers.set("Content-Type", "text/css");
}
return new Response(file, { headers });
}
}
// Handle Remix routes
const loadContext = {};
return handler(request, loadContext);
} catch (error) {
console.error("Error processing request:", error);
return new Response("Internal Server Error", { status: 500 });
}
},
error(error) {
console.error("Server error:", error);
return new Response("Server Error", { status: 500 });
},
});
The above is a simple example but you may want to add a few more things nice-to-haves like xHomu
’s server here.
Finally, we tell Vite to not bundle Node.js built-in modules (since Bun will handle them):
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import { builtinModules } from "module";
export default defineConfig({
optimizeDeps: {
exclude: [...builtinModules],
},
plugins: [
remix({
future: {
v3_fetcherPersist: true,
v3_relativeSplatPath: true,
v3_throwAbortReason: true,
},
}),
tsconfigPaths(),
],
});
At least for my use case, I’m leaving dev
and build
scripts as is:
{
"scripts": {
"build": "remix vite:build",
"dev": "remix vite:dev",
"start": "bun run server.ts",
}
}
Fire off bun run start
and that’s it! You’re now running Remix with Bun. 🚀