From 54b2316b18a8cfb8924c1fcbd74157ce06dc9606 Mon Sep 17 00:00:00 2001 From: "railway-app[bot]" <68434857+railway-app[bot]@users.noreply.github.com> Date: Sun, 5 Apr 2026 06:27:54 +0000 Subject: [PATCH] fix: replace vite preview with node http server in frontend Dockerfile --- frontend/Dockerfile | 2 +- frontend/server.js | 72 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 frontend/server.js diff --git a/frontend/Dockerfile b/frontend/Dockerfile index a29d12f..19eb7ea 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -17,4 +17,4 @@ RUN npm run build EXPOSE 5173 -CMD ["sh", "-c", "npm run preview -- --host 0.0.0.0 --port ${PORT:-5173}"] +CMD ["node", "server.js"] diff --git a/frontend/server.js b/frontend/server.js new file mode 100644 index 0000000..2a04662 --- /dev/null +++ b/frontend/server.js @@ -0,0 +1,72 @@ +import http from "http"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const DIST = path.join(__dirname, "dist"); +const PORT = process.env.PORT || 5173; + +const MIME_TYPES = { + ".html": "text/html; charset=utf-8", + ".js": "application/javascript; charset=utf-8", + ".mjs": "application/javascript; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".svg": "image/svg+xml", + ".webp": "image/webp", + ".ico": "image/x-icon", + ".woff": "font/woff", + ".woff2": "font/woff2", + ".ttf": "font/ttf", + ".otf": "font/otf", + ".txt": "text/plain; charset=utf-8", +}; + +const server = http.createServer((req, res) => { + // Strip query string + const urlPath = req.url.split("?")[0]; + let filePath = path.join(DIST, urlPath); + + // Prevent directory traversal outside dist + if (!filePath.startsWith(DIST)) { + res.writeHead(403); + res.end("Forbidden"); + return; + } + + // Try the exact path, then index.html inside a directory, then SPA fallback + const tryPaths = [filePath, path.join(filePath, "index.html"), path.join(DIST, "index.html")]; + + const tryNext = (paths) => { + if (paths.length === 0) { + res.writeHead(404); + res.end("Not Found"); + return; + } + + const candidate = paths[0]; + fs.stat(candidate, (err, stat) => { + if (err || stat.isDirectory()) { + tryNext(paths.slice(1)); + return; + } + + const ext = path.extname(candidate).toLowerCase(); + const contentType = MIME_TYPES[ext] || "application/octet-stream"; + + res.writeHead(200, { "Content-Type": contentType }); + fs.createReadStream(candidate).pipe(res); + }); + }; + + tryNext(tryPaths); +}); + +server.listen(PORT, "0.0.0.0", () => { + console.log(`Frontend server listening on http://0.0.0.0:${PORT}`); +});