/** * server.js - Next.js custom server for cPanel (Passenger) * Domain: https://banksfinders.com/ * * ✅ cPanel friendly: uses process.env.PORT, no hard-coded public port binding * ✅ Optional .env support (keep secrets in cPanel env vars if possible) * ✅ Custom routes: /a and /b * ✅ Health checks: /health, /ready * ✅ Basic security headers * ✅ Graceful shutdown */ "use strict"; // --- Load .env early (optional) --- try { // If you uploaded a .env in the app root, this will load it. // Best practice in production is to set env vars in cPanel UI instead. require("dotenv").config(); } catch (_) { // Ignore if dotenv is not installed or .env not present } const http = require("http"); const next = require("next"); const dev = process.env.NODE_ENV !== "production"; const port = Number.parseInt(process.env.PORT || "3000", 10); // Passenger routes internally. Keep prod bind local. const hostname = dev ? "0.0.0.0" : "127.0.0.1"; const app = next({ dev, hostname, port }); const handle = app.getRequestHandler(); // ---- helpers ---- function setSecurityHeaders(res) { // Safe defaults that won't usually break Next.js res.setHeader("X-Content-Type-Options", "nosniff"); res.setHeader("X-Frame-Options", "SAMEORIGIN"); res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); res.setHeader("Permissions-Policy", "geolocation=(), microphone=(), camera=()"); // HSTS should be set at the proxy/web server (Apache/Nginx) when HTTPS is confirmed. // If you want it here, uncomment: // res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload"); } function getUrl(req) { // Build a URL object safely even behind proxy const host = req.headers["x-forwarded-host"] || req.headers.host || "localhost"; const proto = req.headers["x-forwarded-proto"] || "http"; return new URL(req.url || "/", `${proto}://${host}`); } function sendText(res, statusCode, text) { res.writeHead(statusCode, { "Content-Type": "text/plain; charset=utf-8" }); res.end(text); } // ---- main ---- app .prepare() .then(() => { const server = http.createServer(async (req, res) => { try { setSecurityHeaders(res); // Health / readiness checks if (req.url === "/health") return sendText(res, 200, "OK"); if (req.url === "/ready") return sendText(res, 200, "READY"); const url = getUrl(req); const pathname = url.pathname; // Convert query params to plain object for Next render() const query = Object.fromEntries(url.searchParams.entries()); // Your custom routes if (pathname === "/a") return app.render(req, res, "/a", query); if (pathname === "/b") return app.render(req, res, "/b", query); // Everything else handled by Next return handle(req, res); } catch (err) { console.error("Error occurred handling", req.url, err); return sendText(res, 500, "Internal Server Error"); } }); server.listen(port, hostname, () => { console.log( `> BanksFinders Next.js server running (env=${dev ? "dev" : "prod"}) on http://${hostname}:${port}` ); console.log("> Public URL: https://banksfinders.com/"); }); // Graceful shutdown (important for Passenger restarts) const shutdown = (signal) => { console.log(`Received ${signal}. Shutting down...`); server.close(() => process.exit(0)); // Force exit after 10s setTimeout(() => process.exit(1), 10_000).unref(); }; process.on("SIGTERM", () => shutdown("SIGTERM")); process.on("SIGINT", () => shutdown("SIGINT")); }) .catch((err) => { console.error("Next.js app failed to prepare:", err); process.exit(1); });