// Minimal production host for the TanStack Start build. // // `vite build` emits a static client (dist/client) and a fetch-style SSR // handler (dist/server/server.js default export). Neither listens on a port, // so this tiny Node server does two jobs: serve files out of dist/client, and // pass everything else through the SSR handler. Listens on PORT (Hugging Face // Spaces routes to 7860). No framework, no extra dependencies. import { createServer } from 'node:http' import { Readable } from 'node:stream' import { stat } from 'node:fs/promises' import { createReadStream, readdirSync } from 'node:fs' import { join, normalize, extname } from 'node:path' import { fileURLToPath } from 'node:url' import handler from './dist/server/server.js' const ROOT = fileURLToPath(new URL('.', import.meta.url)) const CLIENT_DIR = join(ROOT, 'dist', 'client') const ASSETS_DIR = join(CLIENT_DIR, 'assets') const PORT = Number(process.env.PORT) || 3000 const HOST = process.env.HOST || '0.0.0.0' // Map "name-prefix" -> emitted CSS filename. Tailwind v4 + the TanStack Start // SSR build can hash the stylesheet differently between the client and server // passes, so the SSR HTML injects a CSS href whose hash does not match the file // the client actually emitted. Since there is exactly one file per prefix // (styles-*, mail-*), we resolve any missing hashed CSS to the real one. The // hash is only a cache key, so serving the emitted stylesheet is correct. // Key by the base NAME (the part before the first hyphen), not the hash. Vite's // base64url hashes can themselves contain hyphens, so splitting on the last // hyphen is unreliable; the first token (styles, tetris-loader -> "tetris", // mail) is stable and unique enough across the emitted stylesheets. const cssByToken = new Map() try { for (const f of readdirSync(ASSETS_DIR)) { if (f.endsWith('.css')) cssByToken.set(f.split('-')[0], f) } } catch (err) { console.warn('[server] could not index CSS assets:', err.message) } const MIME = { '.js': 'text/javascript', '.mjs': 'text/javascript', '.css': 'text/css', '.html': 'text/html; charset=utf-8', '.json': 'application/json', '.geojson': 'application/json', '.map': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.ico': 'image/x-icon', '.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf', '.wasm': 'application/wasm', } // Serve a file from dist/client if it exists. Returns true if it handled the // request. Guards against path traversal outside the client directory. async function tryStatic(res, pathname) { const safe = normalize(decodeURIComponent(pathname)).replace(/^([/\\])+/, '') const filePath = join(CLIENT_DIR, safe) if (!filePath.startsWith(CLIENT_DIR)) return false try { const info = await stat(filePath) if (!info.isFile()) return false res.writeHead(200, { 'content-type': MIME[extname(filePath)] || 'application/octet-stream', 'content-length': info.size, 'cache-control': 'public, max-age=3600', }) createReadStream(filePath).pipe(res) return true } catch { return false } } // Build a Web Request from the incoming Node request so the SSR handler (which // speaks the Fetch API) can process it. function toWebRequest(req) { const url = `http://${req.headers.host || 'localhost'}${req.url}` const headers = new Headers() for (const [key, value] of Object.entries(req.headers)) { if (value !== undefined) headers.set(key, Array.isArray(value) ? value.join(', ') : value) } const method = req.method || 'GET' const hasBody = method !== 'GET' && method !== 'HEAD' return new Request(url, { method, headers, body: hasBody ? Readable.toWeb(req) : undefined, duplex: hasBody ? 'half' : undefined, }) } const server = createServer(async (req, res) => { try { const pathname = (req.url || '/').split('?')[0] // Static assets first (skip the bare root so it always renders via SSR). if ((req.method === 'GET' || req.method === 'HEAD') && pathname !== '/') { if (await tryStatic(res, pathname)) return // CSS fallback: the SSR build can inject a stylesheet hash that does not // match the emitted file. Resolve it to the real one by name prefix so the // page is never served unstyled. if (pathname.startsWith('/assets/') && pathname.endsWith('.css')) { const token = pathname.slice('/assets/'.length).split('-')[0] const actual = cssByToken.get(token) if (actual && (await tryStatic(res, `/assets/${actual}`))) return } } const response = await handler.fetch(toWebRequest(req)) res.statusCode = response.status response.headers.forEach((value, key) => res.setHeader(key, value)) if (response.body) { Readable.fromWeb(response.body).pipe(res) } else { res.end() } } catch (err) { console.error('[server] request failed:', err) if (!res.headersSent) res.writeHead(500, { 'content-type': 'text/plain' }) res.end('Internal Server Error') } }) server.listen(PORT, HOST, () => { console.log(`GridLoc is listening on http://${HOST}:${PORT}`) })