Skip to content

Instantly share code, notes, and snippets.

@roderik
Created May 31, 2025 16:18
Show Gist options
  • Save roderik/50c4b07a452cc0546b76d66fc957757c to your computer and use it in GitHub Desktop.
Save roderik/50c4b07a452cc0546b76d66fc957757c to your computer and use it in GitHub Desktop.
Bun + ORPC + Tanstack Router setup, without Vite
import { serve } from "bun";
import { serverConfig } from "./lib/config/server";
import "./lib/plugins/tanstack-router-generator";
import { routes } from "./lib/routes";
const server = serve({
routes,
...serverConfig,
});
console.log(`🚀 Server running at ${server.url}`);
import { auth } from "@/server/lib/auth";
import { createApiHandler } from "@/server/lib/handlers/api-handler";
import {
applyMiddleware,
composeMiddleware,
withApiCache,
withCompression,
withRequestId,
withResponseHeaders,
} from "@/server/lib/middleware";
import index from "../../browser/index.html";
// Create API handler with middleware
const apiHandler = applyMiddleware(
[withCompression, withApiCache],
withResponseHeaders(composeMiddleware([withRequestId], createApiHandler())),
);
// Wrap auth handler with compression
const authHandler = applyMiddleware([withCompression], auth.handler);
// For static files, use the HTMLBundle directly without middleware
// since Bun handles compression for static files automatically
const staticHandler = index;
export const routes = {
"/api/auth/*": authHandler,
"/api": apiHandler,
"/api/*": apiHandler,
// Serve index.html for all unmatched routes.
"/*": staticHandler,
};
import type { Config } from "@tanstack/router-generator";
import { type BunPlugin } from "bun";
import { watch, type FSWatcher } from "fs";
import { resolve } from "path";
// Load configuration using TanStack Router generator
async function loadConfig(): Promise<Config> {
const { getConfig } = await import("@tanstack/router-generator");
const config = getConfig();
return {
...config,
disableLogging: true,
};
}
// Generate route tree using TanStack Router generator
async function generateRouteTree(config: Config) {
try {
const { generator } = await import("@tanstack/router-generator");
await generator(config, process.cwd());
} catch {
console.warn("TanStack Router generator not found or failed to generate");
}
}
// Debounce timer storage
let debounceTimer: Timer | null = null;
// Watch for changes in development mode
function watchRoutes(config: Config): FSWatcher | undefined {
const routesDir = resolve(config.routesDirectory);
// Watch the routes directory for changes
const watcher = watch(
routesDir,
{ recursive: true },
async (eventType, filename) => {
if (!filename) return;
// Only react to TypeScript/JavaScript files
if (!/\.(ts|tsx|js|jsx)$/.test(filename)) return;
// Ignore files with the ignore prefix
if (filename.startsWith(config.routeFileIgnorePrefix as string)) return;
// Debounce regeneration
if (debounceTimer) {
clearTimeout(debounceTimer);
}
debounceTimer = setTimeout(async () => {
await generateRouteTree(config);
}, 100);
},
);
return watcher;
}
// Create the Bun plugin
export function createTanStackRouterPlugin(
options: Record<string, unknown> = {},
): BunPlugin {
return {
name: "TanStack Router",
async setup(build) {
// Merge user options with loaded config
const loadedConfig = await loadConfig();
const config = { ...loadedConfig, ...options };
// Determine if we're in development mode
const isDev =
process.env.NODE_ENV !== "production" &&
process.env.BUN_ENV !== "production";
build.onStart(async () => {
// Always generate initially
await generateRouteTree(config);
// Set up watching in development mode
if (isDev) {
watchRoutes(config);
}
});
// Handle route file imports - let TanStack Router handle them naturally
build.onResolve({ filter: /\.route\.(ts|tsx|js|jsx)$/ }, () => {
return undefined;
});
},
};
}
// Export default plugin instance
export default createTanStackRouterPlugin();
// Also run the plugin functionality immediately when imported
(async () => {
try {
const config = await loadConfig();
await generateRouteTree(config);
// Set up watching in development mode
const isDev =
process.env.NODE_ENV !== "production" &&
process.env.BUN_ENV !== "production";
if (isDev) {
watchRoutes(config);
}
} catch (error) {
console.warn("Failed to run TanStack Router plugin:", error);
}
})();
{
"routesDirectory": "./browser/routes",
"generatedRouteTree": "./browser/routeTree.gen.ts",
"routeFileIgnorePrefix": "-",
"quoteStyle": "double"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment