Files
bunext/dist/functions/server/bunext-req-handler.js
tbenandClaude Opus 5.5 96e392f556 1.4.0: not_found, server-only response options, compression
- Server functions can return not_found: true to render the 404 page
  with status 404.
- Page components and window.__PAGE_PROPS__ (and HMR updates) only get
  props, query, url and error. response_options, redirect, cache settings
  and res_transform stay on the server, so cookies and private headers no
  longer end up in the HTML. BunextPageProps is narrowed to match
  (breaking for code that read server-only fields from component props).
- response_options headers go through a Headers object, so repeated
  headers such as several Set-Cookie survive.
- Redirects: status_code wins over permanent; response_options headers
  and res_transform apply to redirects too.
- Gzip for compressible text responses; compress: false to turn it off.
- Files the router matches but never bundles (.server.ts, __root,
  excluded paths and directories) return 404 instead of looping through
  dev rebuild retries.
- Missing public files return 404 rather than 403, so dotted page paths
  like /users/john.doe fall through to pages again.
- importFresh detects rewrites within the filesystem's mtime granularity.
- README: self-hosted React (not esm.sh), snake_case option names, static
  file serving, cache headers, the new options.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 04:14:56 +01:00

107 lines
4.4 KiB
JavaScript

import handleWebPages from "./web-pages/handle-web-pages";
import handleRoutes from "./handle-routes";
import isDevelopment from "../../utils/is-development";
import handleHmr from "./handle-hmr";
import handlePublic from "./handle-public";
import handleFiles from "./handle-files";
import handleBunextPublicAssets from "./handle-bunext-public-assets";
import checkExcludedPatterns from "../../utils/check-excluded-patterns";
import { AppData } from "../../data/app-data";
import { log } from "../../utils/log";
import fullRebuild from "./full-rebuild";
import grabRouter from "../../utils/grab-router";
import grabBunextPublicUrlPath from "../../utils/grab-bunext-public-url-path";
import { NoSEORobotsValue } from "../../utils/disable-seo";
import generateRobotsTxt from "../../utils/generate-robots-txt";
import compressResponse from "../../utils/compress-response";
const HMR_RETRY_COOLDOWN_MS = 5000;
let lastHmrRetryTime = 0;
export default async function bunextRequestHandler({ req: initial_req, server, }) {
const is_dev = isDevelopment();
const middleware = global.BUNEXT_CONSTANTS.config?.middleware;
// Middleware may consume `initial_req`'s body, so it gets its own copy.
// Cloning tees the body stream and buffers whatever one side doesn't
// read, so only pay for it when middleware is configured.
let req = middleware ? initial_req.clone() : initial_req;
try {
let url = new URL(req.url);
if (checkExcludedPatterns({ path: url.pathname })) {
return Response.json({ success: false, msg: `Invalid Path` }, { status: 404 });
}
let response = undefined;
if (middleware) {
const middleware_res = await middleware({
req: initial_req,
url,
});
if (middleware_res instanceof Response) {
return middleware_res;
}
if (middleware_res instanceof Request) {
req = middleware_res;
url = new URL(req.url);
}
}
if (is_dev && url.pathname == AppData["BunextHMRRetryRoute"]) {
const now = Date.now();
if (now - lastHmrRetryTime < HMR_RETRY_COOLDOWN_MS) {
return new Response("Too Many Requests", { status: 429 });
}
lastHmrRetryTime = now;
await fullRebuild({ msg: `HMR Retry Rebuild ...` });
return new Response("Modules Rebuilt");
}
const disable_seo = global.BUNEXT_CONSTANTS.config?.disable_seo;
if (url.pathname === "/__hmr" && is_dev) {
return handleHmr({ req });
}
else if (disable_seo && url.pathname == "/robots.txt") {
response = new Response(generateRobotsTxt({ disallow_all: true }), {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}
else if (url.pathname.startsWith(`${grabBunextPublicUrlPath()}/`)) {
response = await handleBunextPublicAssets({ req });
}
else if (url.pathname.startsWith("/api/")) {
response = await handleRoutes({ req });
}
else if (url.pathname.startsWith("/public/")) {
response = await handlePublic({ req });
}
else if (url.pathname.match(/\.[^/]+$/)) {
response = await handleFiles({ req });
// Paths like `/users/john.doe` look like files but can be pages.
if (response.status == 404 &&
grabRouter()?.match(url.pathname)?.filePath) {
response = await handleWebPages({ req });
}
}
else {
response = await handleWebPages({ req });
}
if (!response) {
throw new Error(`No Response generated`);
}
if (disable_seo) {
response.headers.set("X-Robots-Tag", NoSEORobotsValue);
}
if (is_dev) {
response.headers.set("Cache-Control", "no-cache, no-store, must-revalidate");
}
if (global.BUNEXT_CONFIG?.compress !== false) {
response = compressResponse(req, response);
}
return response;
}
catch (error) {
log.error(`Server Error: ${error?.message || error}\n${error?.stack || ""}`);
if (is_dev) {
return new Response(`Server Error: ${error?.message || error}`, {
status: 500,
});
}
return new Response("Internal Server Error", { status: 500 });
}
}