[{"data":1,"prerenderedAt":1802},["ShallowReactive",2],{"navigation":3,"-guide-middleware":56,"-guide-middleware-surround":1797},[4],{"title":5,"path":6,"stem":7,"children":8,"icon":10},"Getting Started","/guide","1.guide/1.index",[9,11,16,21,26,31,36,41,46,51],{"title":5,"path":6,"stem":7,"icon":10},"ph:book-open-duotone",{"title":12,"path":13,"stem":14,"icon":15},"Using CLI","/guide/cli","1.guide/10.cli","garden:terminal-cli-stroke-12",{"title":17,"path":18,"stem":19,"icon":20},"Fetch Handler","/guide/handler","1.guide/2.handler","i-fluent:target-24-regular",{"title":22,"path":23,"stem":24,"icon":25},"Server Instance","/guide/server","1.guide/3.server","radix-icons:component-instance",{"title":27,"path":28,"stem":29,"icon":30},"Middleware","/guide/middleware","1.guide/4.middleware","clarity:plugin-line",{"title":32,"path":33,"stem":34,"icon":35},"Server Options","/guide/options","1.guide/5.options","ri:settings-3-line",{"title":37,"path":38,"stem":39,"icon":40},"TLS","/guide/tls","1.guide/6.tls","ri:lock-2-line",{"title":42,"path":43,"stem":44,"icon":45},"Bundler Usage","/guide/bundler","1.guide/7.bundler","clarity:bundle-line",{"title":47,"path":48,"stem":49,"icon":50},"Node.js Support","/guide/node","1.guide/8.node","akar-icons:node-fill",{"title":52,"path":53,"stem":54,"icon":55},"AWS Lambda","/guide/aws-lambda","1.guide/9.aws-lambda","clarity:cloud-traffic-line",{"id":57,"title":27,"body":58,"description":1791,"extension":1792,"meta":1793,"navigation":1794,"path":28,"seo":1795,"stem":29,"__hash__":1796},"content/1.guide/4.middleware.md",{"type":59,"value":60,"toc":1781,"icon":30},"minimark",[61,66,451,455,476,486,490,497,591,596,604,628,706,724,737,744,762,809,838,842,865,951,957,963,1157,1193,1242,1277,1288,1305,1354,1380,1405,1412,1435,1450,1457,1461,1481,1485,1516,1733,1754,1767,1777],[62,63,65],"h2",{"id":64},"example","Example",[67,68,73],"pre",{"className":69,"code":70,"language":71,"meta":72,"style":72},"language-ts shiki shiki-themes github-light github-dark github-dark","import { serve, type ServerMiddleware, type ServerPlugin } from \"srvx\";\n\nconst xPoweredBy: ServerMiddleware = async (req, next) => {\n  const res = await next();\n  res.headers.set(\"X-Powered-By\", \"srvx\");\n  return res;\n};\n\nconst devLogs: ServerPlugin = (server) => {\n  if (process.env.NODE_ENV === \"production\") {\n    return;\n  }\n  console.log(`Logger plugin enabled!`);\n  server.options.middleware.push((req, next) => {\n    console.log(`[request] [${req.method}] ${req.url}`);\n    return next();\n  });\n};\n\nserve({\n  middleware: [xPoweredBy],\n  plugins: [devLogs],\n  fetch(request) {\n    return new Response(`👋 Hello there.`);\n  },\n});\n","ts","",[74,75,76,110,117,161,182,205,214,220,225,251,272,280,286,302,326,362,371,377,382,387,396,402,408,421,439,445],"code",{"__ignoreMap":72},[77,78,81,85,89,92,95,97,100,103,107],"span",{"class":79,"line":80},"line",1,[77,82,84],{"class":83},"so5gQ","import",[77,86,88],{"class":87},"slsVL"," { serve, ",[77,90,91],{"class":83},"type",[77,93,94],{"class":87}," ServerMiddleware, ",[77,96,91],{"class":83},[77,98,99],{"class":87}," ServerPlugin } ",[77,101,102],{"class":83},"from",[77,104,106],{"class":105},"sfrk1"," \"srvx\"",[77,108,109],{"class":87},";\n",[77,111,113],{"class":79,"line":112},2,[77,114,116],{"emptyLinePlaceholder":115},true,"\n",[77,118,120,123,127,130,133,136,139,142,146,149,152,155,158],{"class":79,"line":119},3,[77,121,122],{"class":83},"const",[77,124,126],{"class":125},"shcOC"," xPoweredBy",[77,128,129],{"class":83},":",[77,131,132],{"class":125}," ServerMiddleware",[77,134,135],{"class":83}," =",[77,137,138],{"class":83}," async",[77,140,141],{"class":87}," (",[77,143,145],{"class":144},"sQHwn","req",[77,147,148],{"class":87},", ",[77,150,151],{"class":144},"next",[77,153,154],{"class":87},") ",[77,156,157],{"class":83},"=>",[77,159,160],{"class":87}," {\n",[77,162,164,167,171,173,176,179],{"class":79,"line":163},4,[77,165,166],{"class":83},"  const",[77,168,170],{"class":169},"suiK_"," res",[77,172,135],{"class":83},[77,174,175],{"class":83}," await",[77,177,178],{"class":125}," next",[77,180,181],{"class":87},"();\n",[77,183,185,188,191,194,197,199,202],{"class":79,"line":184},5,[77,186,187],{"class":87},"  res.headers.",[77,189,190],{"class":125},"set",[77,192,193],{"class":87},"(",[77,195,196],{"class":105},"\"X-Powered-By\"",[77,198,148],{"class":87},[77,200,201],{"class":105},"\"srvx\"",[77,203,204],{"class":87},");\n",[77,206,208,211],{"class":79,"line":207},6,[77,209,210],{"class":83},"  return",[77,212,213],{"class":87}," res;\n",[77,215,217],{"class":79,"line":216},7,[77,218,219],{"class":87},"};\n",[77,221,223],{"class":79,"line":222},8,[77,224,116],{"emptyLinePlaceholder":115},[77,226,228,230,233,235,238,240,242,245,247,249],{"class":79,"line":227},9,[77,229,122],{"class":83},[77,231,232],{"class":125}," devLogs",[77,234,129],{"class":83},[77,236,237],{"class":125}," ServerPlugin",[77,239,135],{"class":83},[77,241,141],{"class":87},[77,243,244],{"class":144},"server",[77,246,154],{"class":87},[77,248,157],{"class":83},[77,250,160],{"class":87},[77,252,254,257,260,263,266,269],{"class":79,"line":253},10,[77,255,256],{"class":83},"  if",[77,258,259],{"class":87}," (process.env.",[77,261,262],{"class":169},"NODE_ENV",[77,264,265],{"class":83}," ===",[77,267,268],{"class":105}," \"production\"",[77,270,271],{"class":87},") {\n",[77,273,275,278],{"class":79,"line":274},11,[77,276,277],{"class":83},"    return",[77,279,109],{"class":87},[77,281,283],{"class":79,"line":282},12,[77,284,285],{"class":87},"  }\n",[77,287,289,292,295,297,300],{"class":79,"line":288},13,[77,290,291],{"class":87},"  console.",[77,293,294],{"class":125},"log",[77,296,193],{"class":87},[77,298,299],{"class":105},"`Logger plugin enabled!`",[77,301,204],{"class":87},[77,303,305,308,311,314,316,318,320,322,324],{"class":79,"line":304},14,[77,306,307],{"class":87},"  server.options.middleware.",[77,309,310],{"class":125},"push",[77,312,313],{"class":87},"((",[77,315,145],{"class":144},[77,317,148],{"class":87},[77,319,151],{"class":144},[77,321,154],{"class":87},[77,323,157],{"class":83},[77,325,160],{"class":87},[77,327,329,332,334,336,339,341,344,347,350,352,354,357,360],{"class":79,"line":328},15,[77,330,331],{"class":87},"    console.",[77,333,294],{"class":125},[77,335,193],{"class":87},[77,337,338],{"class":105},"`[request] [${",[77,340,145],{"class":87},[77,342,343],{"class":105},".",[77,345,346],{"class":87},"method",[77,348,349],{"class":105},"}] ${",[77,351,145],{"class":87},[77,353,343],{"class":105},[77,355,356],{"class":87},"url",[77,358,359],{"class":105},"}`",[77,361,204],{"class":87},[77,363,365,367,369],{"class":79,"line":364},16,[77,366,277],{"class":83},[77,368,178],{"class":125},[77,370,181],{"class":87},[77,372,374],{"class":79,"line":373},17,[77,375,376],{"class":87},"  });\n",[77,378,380],{"class":79,"line":379},18,[77,381,219],{"class":87},[77,383,385],{"class":79,"line":384},19,[77,386,116],{"emptyLinePlaceholder":115},[77,388,390,393],{"class":79,"line":389},20,[77,391,392],{"class":125},"serve",[77,394,395],{"class":87},"({\n",[77,397,399],{"class":79,"line":398},21,[77,400,401],{"class":87},"  middleware: [xPoweredBy],\n",[77,403,405],{"class":79,"line":404},22,[77,406,407],{"class":87},"  plugins: [devLogs],\n",[77,409,411,414,416,419],{"class":79,"line":410},23,[77,412,413],{"class":125},"  fetch",[77,415,193],{"class":87},[77,417,418],{"class":144},"request",[77,420,271],{"class":87},[77,422,424,426,429,432,434,437],{"class":79,"line":423},24,[77,425,277],{"class":83},[77,427,428],{"class":83}," new",[77,430,431],{"class":125}," Response",[77,433,193],{"class":87},[77,435,436],{"class":105},"`👋 Hello there.`",[77,438,204],{"class":87},[77,440,442],{"class":79,"line":441},25,[77,443,444],{"class":87},"  },\n",[77,446,448],{"class":79,"line":447},26,[77,449,450],{"class":87},"});\n",[62,452,454],{"id":453},"order-of-execution","Order of execution",[456,457,458,459,462,463,466,467,471,472,475],"p",{},"Middleware run in the order they appear in the ",[74,460,461],{},"middleware"," array, each wrapping the next, with your ",[74,464,465],{},"fetch"," handler at the center. A middleware that returns a response ",[468,469,470],"strong",{},"without"," calling ",[74,473,474],{},"next()"," short-circuits the rest of the chain — nothing after it runs.",[456,477,478,479,482,483,485],{},"Plugins are applied in ",[74,480,481],{},"plugins"," array order, before the server starts listening. A plugin that pushes middleware appends to the end of the array, so ",[74,484,461],{}," entries always run first.",[62,487,489],{"id":488},"built-in-middleware-and-plugins","Built-in middleware and plugins",[456,491,492,493,496],{},"srvx ships several optional extensions as separate subpath imports. All of them are opt-in — importing ",[74,494,495],{},"srvx"," alone pulls in none of them.",[498,499,500,519],"table",{},[501,502,503],"thead",{},[504,505,506,510,513,516],"tr",{},[507,508,509],"th",{},"Import",[507,511,512],{},"Export",[507,514,515],{},"Kind",[507,517,518],{},"Runtimes",[520,521,522,540,557,575],"tbody",{},[504,523,524,530,535,537],{},[525,526,527],"td",{},[74,528,529],{},"srvx/log",[525,531,532],{},[74,533,534],{},"log()",[525,536,27],{},[525,538,539],{},"All",[504,541,542,547,552,554],{},[525,543,544],{},[74,545,546],{},"srvx/static",[525,548,549],{},[74,550,551],{},"serveStatic()",[525,553,27],{},[525,555,556],{},"Node, Deno, Bun",[504,558,559,564,569,572],{},[525,560,561],{},[74,562,563],{},"srvx/mtls",[525,565,566],{},[74,567,568],{},"mtls()",[525,570,571],{},"Plugin",[525,573,574],{},"Node",[504,576,577,582,587,589],{},[525,578,579],{},[74,580,581],{},"srvx/tracing",[525,583,584],{},[74,585,586],{},"tracingPlugin()",[525,588,571],{},[525,590,556],{},[592,593,595],"h3",{"id":594},"request-logging","Request logging",[456,597,598,600,601,603],{},[74,599,534],{}," from ",[74,602,529],{}," prints one line per request with the method, URL, status, and duration.",[456,605,606,607,609,610,613,614,623,624,627],{},"Colors are disabled when ",[74,608,262],{}," is ",[74,611,612],{},"production",", as well as when ",[615,616,620],"a",{"href":617,"rel":618},"https://no-color.org/",[619],"nofollow",[74,621,622],{},"NO_COLOR"," is set or output is not a TTY. Set ",[74,625,626],{},"FORCE_COLOR"," to keep them in any of those cases.",[67,629,632],{"className":69,"code":630,"filename":631,"language":71,"meta":72,"style":72},"import { serve } from \"srvx\";\nimport { log } from \"srvx/log\";\n\nserve({\n  middleware: [log()],\n  fetch: () => new Response(\"👋 Hello there.\"),\n});\n","server.ts",[74,633,634,647,661,665,671,681,702],{"__ignoreMap":72},[77,635,636,638,641,643,645],{"class":79,"line":80},[77,637,84],{"class":83},[77,639,640],{"class":87}," { serve } ",[77,642,102],{"class":83},[77,644,106],{"class":105},[77,646,109],{"class":87},[77,648,649,651,654,656,659],{"class":79,"line":112},[77,650,84],{"class":83},[77,652,653],{"class":87}," { log } ",[77,655,102],{"class":83},[77,657,658],{"class":105}," \"srvx/log\"",[77,660,109],{"class":87},[77,662,663],{"class":79,"line":119},[77,664,116],{"emptyLinePlaceholder":115},[77,666,667,669],{"class":79,"line":163},[77,668,392],{"class":125},[77,670,395],{"class":87},[77,672,673,676,678],{"class":79,"line":184},[77,674,675],{"class":87},"  middleware: [",[77,677,294],{"class":125},[77,679,680],{"class":87},"()],\n",[77,682,683,685,688,690,692,694,696,699],{"class":79,"line":207},[77,684,413],{"class":125},[77,686,687],{"class":87},": () ",[77,689,157],{"class":83},[77,691,428],{"class":83},[77,693,431],{"class":125},[77,695,193],{"class":87},[77,697,698],{"class":105},"\"👋 Hello there.\"",[77,700,701],{"class":87},"),\n",[77,703,704],{"class":79,"line":216},[77,705,450],{"class":87},[67,707,711],{"className":708,"code":709,"language":710,"meta":72,"style":72},"language-sh shiki shiki-themes github-light github-dark github-dark","[10:32:03 AM] GET http://localhost:3000/ [200] (1.42ms)\n","sh",[74,712,713],{"__ignoreMap":72},[77,714,715,718,721],{"class":79,"line":80},[77,716,717],{"class":87},"[10:32:03 AM] GET http://localhost:3000/ [200] (",[77,719,720],{"class":125},"1.42ms",[77,722,723],{"class":87},")\n",[456,725,726,727,729,730,732,733,736],{},"The duration is measured around ",[74,728,474],{},", so place ",[74,731,534],{}," first in the array for it to cover the whole chain. The ",[615,734,735],{"href":13},"CLI"," enables this middleware automatically.",[456,738,739],{},[468,740,741,743],{},[74,742,534],{}," options:",[745,746,747],"ul",{},[748,749,750,753,754,757,758,761],"li",{},[74,751,752],{},"batch",": Coalesce lines and write once per event-loop turn (default: ",[74,755,756],{},"true","). Set to ",[74,759,760],{},"false"," to hand each line to stdout as its request completes — slower under concurrency, but a line never waits for a flush turn.",[67,763,765],{"className":69,"code":764,"language":71,"meta":72,"style":72},"serve({\n  middleware: [log({ batch: false })],\n  fetch: () => new Response(\"👋 Hello there.\"),\n});\n",[74,766,767,773,787,805],{"__ignoreMap":72},[77,768,769,771],{"class":79,"line":80},[77,770,392],{"class":125},[77,772,395],{"class":87},[77,774,775,777,779,782,784],{"class":79,"line":112},[77,776,675],{"class":87},[77,778,294],{"class":125},[77,780,781],{"class":87},"({ batch: ",[77,783,760],{"class":169},[77,785,786],{"class":87}," })],\n",[77,788,789,791,793,795,797,799,801,803],{"class":79,"line":119},[77,790,413],{"class":125},[77,792,687],{"class":87},[77,794,157],{"class":83},[77,796,428],{"class":83},[77,798,431],{"class":125},[77,800,193],{"class":87},[77,802,698],{"class":105},[77,804,701],{"class":87},[77,806,807],{"class":79,"line":163},[77,808,450],{"class":87},[456,810,811,814,815,818,819,822,823,822,826,829,830,833,834,837],{},[468,812,813],{},"Crash safety:"," buffered lines are flushed when the process ends — on normal exit, ",[74,816,817],{},"process.exit()",", uncaught exceptions, and ",[74,820,821],{},"SIGHUP","/",[74,824,825],{},"SIGTERM",[74,827,828],{},"SIGINT",", whether a shutdown handler (such as srvx's graceful shutdown) is installed or the signal is default-handled. Only a hard kill (",[74,831,832],{},"SIGKILL",", the OOM killer) can drop the lines of the final event-loop turn; ",[74,835,836],{},"batch: false"," narrows that window to the single write in flight.",[592,839,841],{"id":840},"static-files","Static files",[456,843,844,600,846,848,849,852,853,856,857,860,861,864],{},[74,845,551],{},[74,847,546],{}," serves files from a directory, with ",[74,850,851],{},"index.html"," resolution, ",[74,854,855],{},".html"," extension fallback (",[74,858,859],{},"/about"," → ",[74,862,863],{},"about.html","), common MIME types, gzip/Brotli compression, and path-traversal protection.",[67,866,868],{"className":69,"code":867,"filename":631,"language":71,"meta":72,"style":72},"import { serve } from \"srvx\";\nimport { serveStatic } from \"srvx/static\";\n\nserve({\n  middleware: [serveStatic({ dir: \"public\" })],\n  fetch: () => new Response(\"Not found\", { status: 404 }),\n});\n",[74,869,870,882,896,900,906,921,947],{"__ignoreMap":72},[77,871,872,874,876,878,880],{"class":79,"line":80},[77,873,84],{"class":83},[77,875,640],{"class":87},[77,877,102],{"class":83},[77,879,106],{"class":105},[77,881,109],{"class":87},[77,883,884,886,889,891,894],{"class":79,"line":112},[77,885,84],{"class":83},[77,887,888],{"class":87}," { serveStatic } ",[77,890,102],{"class":83},[77,892,893],{"class":105}," \"srvx/static\"",[77,895,109],{"class":87},[77,897,898],{"class":79,"line":119},[77,899,116],{"emptyLinePlaceholder":115},[77,901,902,904],{"class":79,"line":163},[77,903,392],{"class":125},[77,905,395],{"class":87},[77,907,908,910,913,916,919],{"class":79,"line":184},[77,909,675],{"class":87},[77,911,912],{"class":125},"serveStatic",[77,914,915],{"class":87},"({ dir: ",[77,917,918],{"class":105},"\"public\"",[77,920,786],{"class":87},[77,922,923,925,927,929,931,933,935,938,941,944],{"class":79,"line":207},[77,924,413],{"class":125},[77,926,687],{"class":87},[77,928,157],{"class":83},[77,930,428],{"class":83},[77,932,431],{"class":125},[77,934,193],{"class":87},[77,936,937],{"class":105},"\"Not found\"",[77,939,940],{"class":87},", { status: ",[77,942,943],{"class":169},"404",[77,945,946],{"class":87}," }),\n",[77,948,949],{"class":79,"line":216},[77,950,450],{"class":87},[456,952,953,954,956],{},"When no file matches the request, it calls ",[74,955,474],{}," — so your handler acts as the fallback for unmatched paths.",[456,958,959],{},[468,960,961,743],{},[74,962,551],{},[745,964,965,971,983,1019,1039,1050,1071,1088,1105,1124,1137],{},[748,966,967,970],{},[74,968,969],{},"dir",": The directory to serve files from (required).",[748,972,973,976,977,980,981,343],{},[74,974,975],{},"methods",": HTTP methods to serve (default ",[74,978,979],{},"[\"GET\", \"HEAD\"]","). Other methods fall through to ",[74,982,474],{},[748,984,985,988,989,991,992,995,996,148,999,1002,1003,1005,1006,1008,1009,1011,1012,1015,1016,343],{},[74,986,987],{},"dotfiles",": Dot segments (path segments starting with ",[74,990,343],{},") that may be served (default ",[74,993,994],{},"[\".well-known\"]","). A path containing any other dot segment — ",[74,997,998],{},"/.env",[74,1000,1001],{},"/.git/config"," — falls through to ",[74,1004,474],{},". Pass ",[74,1007,756],{}," to serve every dot segment, or ",[74,1010,760],{}," (or ",[74,1013,1014],{},"[]",") to serve none, including ",[74,1017,1018],{},"/.well-known/",[748,1020,1021,1024,1025,1027,1028,1030,1031,1034,1035,1038],{},[74,1022,1023],{},"encodings",": Serve precompressed variants from disk (default ",[74,1026,760],{},"). Pass ",[74,1029,756],{}," for ",[74,1032,1033],{},"{ br: \".br\", gzip: \".gz\" }",", or a map setting the extension per encoding (keys tried in order, preferred first). Off by default because most deployments ship no precompressed files, so the lookup is a ",[74,1036,1037],{},"stat"," that always misses.",[748,1040,1041,1044,1045,1027,1047,1049],{},[74,1042,1043],{},"compress",": Compress a response on the fly when no precompressed variant is served (default ",[74,1046,756],{},[74,1048,760],{}," to serve only what is already on disk.",[748,1051,1052,1055,1056,1059,1060,1063,1064,1067,1068,1070],{},[74,1053,1054],{},"lastModified",": Emit a ",[74,1057,1058],{},"Last-Modified"," header from the file's modification time, and answer a matching ",[74,1061,1062],{},"If-Modified-Since"," request with ",[74,1065,1066],{},"304 Not Modified"," (default ",[74,1069,756],{},").",[748,1072,1073,1076,1077,1080,1081,1063,1084,1067,1086,1070],{},[74,1074,1075],{},"etag",": Emit a weak ",[74,1078,1079],{},"ETag"," validator, and answer a matching ",[74,1082,1083],{},"If-None-Match",[74,1085,1066],{},[74,1087,756],{},[748,1089,1090,1093,1094,1097,1098,1067,1101,1104],{},[74,1091,1092],{},"maxAge",": Freshness lifetime in ",[468,1095,1096],{},"seconds",", emitted as ",[74,1099,1100],{},"Cache-Control: max-age=\u003Cn>",[74,1102,1103],{},"undefined",", no header). Lets a client reuse a response without a request until it goes stale.",[748,1106,1107,1110,1111,1113,1114,1117,1118,1120,1121,1123],{},[74,1108,1109],{},"immutable",": Add the ",[74,1112,1109],{}," directive to ",[74,1115,1116],{},"Cache-Control",", so a client does not revalidate a still-fresh response even on reload (default ",[74,1119,760],{},"). Only takes effect alongside ",[74,1122,1092],{},", and only makes sense for fingerprinted (content-hashed) assets.",[748,1125,1126,1129,1130,1132,1133,1136],{},[74,1127,1128],{},"dirListing",": Serve a minimal HTML directory listing as a 404 fallback for a directory with no index file (default ",[74,1131,760],{},"). The rest of the app answers first; only a 404 is replaced by the listing. Off by default because it exposes the directory structure — it is opt-in. The ",[615,1134,735],{"href":1135},"/guide/cli#serving-static-files"," turns it on in dev mode by default.",[748,1138,1139,1142,1143,1146,1147,148,1149,1152,1153,1156],{},[74,1140,1141],{},"renderHTML",": A function receiving ",[74,1144,1145],{},"{ request, html, filename }"," for every HTML file (",[74,1148,855],{},[74,1150,1151],{},".htm","), returning the ",[74,1154,1155],{},"Response"," to send. Use it to inject or template markup before serving.",[456,1158,1159,1160,1163,1164,1167,1168,1170,1171,1173,1174,1177,1178,1181,1182,1185,1186,1189,1190,343],{},"A request resolves in order: the path itself, then ",[74,1161,1162],{},"\u003Cpath>.html",", then ",[74,1165,1166],{},"\u003Cpath>/index.html",". So ",[74,1169,859],{}," serves ",[74,1172,863],{},", while an extension-less file (",[74,1175,1176],{},"LICENSE",", an ACME challenge token) is served at its exact name. A trailing-slash request names a directory, so ",[74,1179,1180],{},"/sub/"," resolves only ",[74,1183,1184],{},"sub/index.html"," — never ",[74,1187,1188],{},"sub.html"," or a file named ",[74,1191,1192],{},"sub",[456,1194,1195,1196,1199,1200,1202,1203,1205,1206,1208,1209,148,1212,1215,1216,1219,1220,1223,1224,1227,1228,148,1231,1234,1235,1238,1239,1241],{},"With ",[74,1197,1198],{},"dirListing: true",", a request naming a directory with no index file — the root, a trailing-slash path, or an extension-less one — still falls through to ",[74,1201,474],{}," first, and a generated HTML listing replaces the response only when it comes back ",[74,1204,943],{},". A real route always wins over a listing, and a custom 404 page keeps working: it is replaced only when the path actually names a listable directory, and passes through untouched everywhere else. An index always wins, so a directory with an ",[74,1207,851],{}," still serves it. Entries are the directory's immediate children, sorted directories-first; a denied dot segment (",[74,1210,1211],{},".env",[74,1213,1214],{},".git",") is hidden from the listing exactly as it is from a direct request, so a listing never names anything the middleware would refuse to serve. Links are absolute paths, so they resolve the same whether the directory was requested with a trailing slash or without, and the page follows the OS light/dark theme. The listing carries ",[74,1217,1218],{},"X-Robots-Tag: noindex, nofollow"," (mirrored by a ",[74,1221,1222],{},"robots"," meta tag) and a strict ",[74,1225,1226],{},"Content-Security-Policy"," — it is a self-contained page with no scripts or external resources — plus ",[74,1229,1230],{},"X-Content-Type-Options: nosniff",[74,1232,1233],{},"Referrer-Policy: no-referrer",", and ",[74,1236,1237],{},"Cache-Control: no-store"," so a browser never shows a stale listing after files change. It is off by default because it reveals the directory structure; the ",[615,1240,735],{"href":1135}," enables it in dev mode by default.",[456,1243,1244,1245,1247,1248,1251,1252,148,1255,1258,1259,1262,1263,1266,1267,1270,1271,1273,1274,1276],{},"By default a compressible response is compressed on the fly as it is sent. Enabling ",[74,1246,1023],{}," adds a disk lookup that takes precedence: for ",[74,1249,1250],{},"/app.js"," with ",[74,1253,1254],{},"Accept-Encoding: br",[74,1256,1257],{},"app.js.br"," is served if it exists (with ",[74,1260,1261],{},"Content-Encoding: br","), and only a missing variant falls back to on-the-fly. A variant always wins because it costs no CPU, and a build can afford a better ratio than a per-request encode can justify — so ",[74,1264,1265],{},"encodings: true"," plus a build step is the cheapest way to serve maximum-quality compressed assets. The two switches are independent: ",[74,1268,1269],{},"compress: false"," serves only what is on disk, and ",[74,1272,1023],{}," off with ",[74,1275,1043],{}," on always compresses on the fly.",[456,1278,1279,1280,1283,1284,1287],{},"Brotli compresses at quality 4 rather than the ",[74,1281,1282],{},"node:zlib"," default of 11, which costs roughly 12x the CPU for a few percent of size. Only files between 1 KiB and 10 MiB are compressed on the fly: below that the encoded body can come out larger than the input, and above it the CPU spent per request outweighs the bandwidth saved — precompress large assets instead. On-the-fly responses are chunked, since the encoded length is not known until the bytes exist, while a variant declares the ",[74,1285,1286],{},"Content-Length"," it has on disk.",[456,1289,1290,1291,1294,1295,1298,1299,1301,1302,1304],{},"Compression applies to compressible types only, so a ",[74,1292,1293],{},".br"," next to an image or font is ignored, and those responses omit ",[74,1296,1297],{},"Vary: Accept-Encoding"," — which compressible ones always set, including uncompressed ones, as a shared cache must key on the header either way. ",[74,1300,1141],{}," routes are never compressed and always read the source file: a variant on disk would not match the rendered output, and the ",[74,1303,1155],{}," the hook returns is the caller's to encode.",[456,1306,1307,1308,1310,1311,1313,1314,1316,1317,1319,1320,1322,1323,1325,1326,1331,1332,1334,1335,1338,1339,1342,1343,1346,1347,1350,1351,1353],{},"Every file served without ",[74,1309,1141],{}," carries an ",[74,1312,1079],{}," and a ",[74,1315,1058],{}," header, and a conditional request that still matches is answered with an empty ",[74,1318,1066],{}," before the body is ever read. ",[74,1321,1083],{}," takes precedence over ",[74,1324,1062],{},", matching ",[615,1327,1330],{"href":1328,"rel":1329},"https://www.rfc-editor.org/rfc/rfc9110#section-13.2.2",[619],"RFC 9110",". The ",[74,1333,1079],{}," is weak (",[74,1336,1337],{},"W/\"…\"","): it is derived from the file's size and modification time rather than its bytes, and folds in the ",[74,1340,1341],{},"Content-Encoding"," so a brotli and a gzip response under one URL get distinct validators. Pass ",[74,1344,1345],{},"etag: false"," or ",[74,1348,1349],{},"lastModified: false"," to drop either header and stop honoring its conditional. ",[74,1352,1141],{}," routes carry neither, since the rendered body is the caller's to validate.",[456,1355,1356,1358,1359,1361,1362,1364,1365,1368,1369,1372,1373,1376,1377,1379],{},[74,1357,1116],{}," is opt-in and off by default, so a client revalidates with those validators on every use. Set ",[74,1360,1092],{}," (in seconds) to send ",[74,1363,1100],{}," and let a client reuse a response without a request until it goes stale; add ",[74,1366,1367],{},"immutable: true"," to send ",[74,1370,1371],{},"max-age=\u003Cn>, immutable",", which also skips revalidation on an explicit reload — appropriate for a fingerprinted asset whose URL changes when its bytes do. The header rides along on the ",[74,1374,1375],{},"304"," too, so a revalidation refreshes the stored freshness. Like the validators, it is omitted on ",[74,1378,1141],{}," routes.",[456,1381,1382,1384,1385,1390,1391,1394,1395,1397,1398,1401,1402,1070],{},[74,1383,1018],{}," is served by default because ",[615,1386,1389],{"href":1387,"rel":1388},"https://www.rfc-editor.org/rfc/rfc8615",[619],"RFC 8615"," reserves it for public metadata: ACME HTTP-01 challenges and ",[74,1392,1393],{},"security.txt"," live there. Allow-listing is by exact segment name, so ",[74,1396,994],{}," serves neither a sibling sharing its prefix (",[74,1399,1400],{},".well-known-backup",") nor a dot segment nested under it (",[74,1403,1404],{},".well-known/.env",[456,1406,1407,1408,1411],{},"Text responses declare ",[74,1409,1410],{},"charset=utf-8","; without it a browser decodes them with a fallback of its own choosing, mangling any non-ASCII byte the file does not declare inline.",[1413,1414,1415],"note",{},[456,1416,1417,1418,1420,1421,1423,1424,1426,1427,860,1430,1002,1432,1434],{},"\nFiles are only served from within ",[74,1419,969],{},", and both rules above are re-checked against the path a symlink actually resolves to. Symlinks are followed, but one resolving outside ",[74,1422,969],{}," — or onto a dot segment ",[74,1425,987],{}," hides, such as ",[74,1428,1429],{},"public.txt",[74,1431,1211],{},[74,1433,474],{}," instead of being served.",[1413,1436,1437],{},[456,1438,1439,1440,609,1442,1445,1446,1449],{},"\nDespite the runtime-neutral name, ",[74,1441,546],{},[468,1443,1444],{},"Node-API-only"," — it uses ",[74,1447,1448],{},"node:fs"," internally, so it works only on runtimes with Node.js compatibility (Node, Deno, Bun).",[456,1451,1452,1453,1456],{},"See ",[615,1454,1455],{"href":1135},"Serving static files"," for the equivalent CLI flag.",[592,1458,1460],{"id":1459},"mutual-tls","Mutual TLS",[456,1462,1463,600,1468,1470,1471,1477,1478,343],{},[615,1464,1466],{"href":1465},"/guide/tls#mutual-tls-mtls",[74,1467,568],{},[74,1469,563],{}," requests a client certificate during the TLS handshake and exposes it on ",[615,1472,1474],{"href":1473},"/guide/handler#requesttls",[74,1475,1476],{},"request.tls",". It requires the ",[615,1479,1480],{"href":48},"Node.js adapter",[592,1482,1484],{"id":1483},"tracing","Tracing",[456,1486,1487,600,1489,1491,1492,1494,1495,1502,1503,1506,1507,1510,1511,343],{},[74,1488,586],{},[74,1490,581],{}," wraps your ",[74,1493,465],{}," handler and each middleware with ",[615,1496,1499],{"href":1497,"rel":1498},"https://nodejs.org/api/diagnostics_channel.html",[619],[74,1500,1501],{},"diagnostics_channel"," instrumentation, publishing to the ",[74,1504,1505],{},"srvx.request"," and ",[74,1508,1509],{},"srvx.middleware"," ",[615,1512,1515],{"href":1513,"rel":1514},"https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel",[619],"tracing channels",[67,1517,1519],{"className":69,"code":1518,"filename":631,"language":71,"meta":72,"style":72},"import { serve } from \"srvx\";\nimport { tracingPlugin } from \"srvx/tracing\";\nimport { tracingChannel } from \"node:diagnostics_channel\";\n\ntracingChannel(\"srvx.request\").subscribe({\n  start: ({ request }) => console.log(`[start] ${request.url}`),\n  asyncEnd: ({ request }) => console.log(`[end] ${request.url}`),\n  error: ({ request, error }) => console.error(`[error] ${request.url}`, error),\n});\n\nserve({\n  plugins: [tracingPlugin()],\n  fetch: () => new Response(\"👋 Hello there.\"),\n});\n",[74,1520,1521,1533,1547,1561,1565,1582,1617,1649,1687,1691,1695,1701,1711,1729],{"__ignoreMap":72},[77,1522,1523,1525,1527,1529,1531],{"class":79,"line":80},[77,1524,84],{"class":83},[77,1526,640],{"class":87},[77,1528,102],{"class":83},[77,1530,106],{"class":105},[77,1532,109],{"class":87},[77,1534,1535,1537,1540,1542,1545],{"class":79,"line":112},[77,1536,84],{"class":83},[77,1538,1539],{"class":87}," { tracingPlugin } ",[77,1541,102],{"class":83},[77,1543,1544],{"class":105}," \"srvx/tracing\"",[77,1546,109],{"class":87},[77,1548,1549,1551,1554,1556,1559],{"class":79,"line":119},[77,1550,84],{"class":83},[77,1552,1553],{"class":87}," { tracingChannel } ",[77,1555,102],{"class":83},[77,1557,1558],{"class":105}," \"node:diagnostics_channel\"",[77,1560,109],{"class":87},[77,1562,1563],{"class":79,"line":163},[77,1564,116],{"emptyLinePlaceholder":115},[77,1566,1567,1570,1572,1575,1577,1580],{"class":79,"line":184},[77,1568,1569],{"class":125},"tracingChannel",[77,1571,193],{"class":87},[77,1573,1574],{"class":105},"\"srvx.request\"",[77,1576,1070],{"class":87},[77,1578,1579],{"class":125},"subscribe",[77,1581,395],{"class":87},[77,1583,1584,1587,1590,1592,1595,1597,1600,1602,1604,1607,1609,1611,1613,1615],{"class":79,"line":207},[77,1585,1586],{"class":125},"  start",[77,1588,1589],{"class":87},": ({ ",[77,1591,418],{"class":144},[77,1593,1594],{"class":87}," }) ",[77,1596,157],{"class":83},[77,1598,1599],{"class":87}," console.",[77,1601,294],{"class":125},[77,1603,193],{"class":87},[77,1605,1606],{"class":105},"`[start] ${",[77,1608,418],{"class":87},[77,1610,343],{"class":105},[77,1612,356],{"class":87},[77,1614,359],{"class":105},[77,1616,701],{"class":87},[77,1618,1619,1622,1624,1626,1628,1630,1632,1634,1636,1639,1641,1643,1645,1647],{"class":79,"line":216},[77,1620,1621],{"class":125},"  asyncEnd",[77,1623,1589],{"class":87},[77,1625,418],{"class":144},[77,1627,1594],{"class":87},[77,1629,157],{"class":83},[77,1631,1599],{"class":87},[77,1633,294],{"class":125},[77,1635,193],{"class":87},[77,1637,1638],{"class":105},"`[end] ${",[77,1640,418],{"class":87},[77,1642,343],{"class":105},[77,1644,356],{"class":87},[77,1646,359],{"class":105},[77,1648,701],{"class":87},[77,1650,1651,1654,1656,1658,1660,1663,1665,1667,1669,1671,1673,1676,1678,1680,1682,1684],{"class":79,"line":222},[77,1652,1653],{"class":125},"  error",[77,1655,1589],{"class":87},[77,1657,418],{"class":144},[77,1659,148],{"class":87},[77,1661,1662],{"class":144},"error",[77,1664,1594],{"class":87},[77,1666,157],{"class":83},[77,1668,1599],{"class":87},[77,1670,1662],{"class":125},[77,1672,193],{"class":87},[77,1674,1675],{"class":105},"`[error] ${",[77,1677,418],{"class":87},[77,1679,343],{"class":105},[77,1681,356],{"class":87},[77,1683,359],{"class":105},[77,1685,1686],{"class":87},", error),\n",[77,1688,1689],{"class":79,"line":227},[77,1690,450],{"class":87},[77,1692,1693],{"class":79,"line":253},[77,1694,116],{"emptyLinePlaceholder":115},[77,1696,1697,1699],{"class":79,"line":274},[77,1698,392],{"class":125},[77,1700,395],{"class":87},[77,1702,1703,1706,1709],{"class":79,"line":282},[77,1704,1705],{"class":87},"  plugins: [",[77,1707,1708],{"class":125},"tracingPlugin",[77,1710,680],{"class":87},[77,1712,1713,1715,1717,1719,1721,1723,1725,1727],{"class":79,"line":288},[77,1714,413],{"class":125},[77,1716,687],{"class":87},[77,1718,157],{"class":83},[77,1720,428],{"class":83},[77,1722,431],{"class":125},[77,1724,193],{"class":87},[77,1726,698],{"class":105},[77,1728,701],{"class":87},[77,1730,1731],{"class":79,"line":304},[77,1732,450],{"class":87},[456,1734,1735,1736,1739,1740,1743,1744,1746,1747,1346,1750,1753],{},"Each event carries ",[74,1737,1738],{},"{ server, request }",", plus ",[74,1741,1742],{},"{ middleware: { index, handler } }"," on the ",[74,1745,1509],{}," channel. Pass ",[74,1748,1749],{},"{ fetch: false }",[74,1751,1752],{},"{ middleware: false }"," to instrument only one of the two.",[456,1755,1756,1757,1759,1760,1763,1764,1766],{},"Because plugins run in order, ",[74,1758,586],{}," only wraps middleware registered before it — keep it ",[468,1761,1762],{},"last"," in the ",[74,1765,481],{}," array so it covers middleware added by earlier plugins.",[1768,1769,1770],"important",{},[456,1771,1772,609,1774,343],{},[74,1773,581],{},[468,1775,1776],{},"experimental",[1778,1779,1780],"style",{},"html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":72,"searchDepth":112,"depth":112,"links":1782},[1783,1784,1785],{"id":64,"depth":112,"text":65},{"id":453,"depth":112,"text":454},{"id":488,"depth":112,"text":489,"children":1786},[1787,1788,1789,1790],{"id":594,"depth":119,"text":595},{"id":840,"depth":119,"text":841},{"id":1459,"depth":119,"text":1460},{"id":1483,"depth":119,"text":1484},"Plugins and middleware allow adding reusable server extensions.","md",{"icon":30},{"icon":30},{"title":27,"description":1791},"Nb7dOk-08gWVcgkC_EU-BzmTIFWfbtgshqc8fY0IQYA",[1798,1800],{"title":22,"path":23,"stem":24,"description":1799,"icon":25,"children":-1},"Control srvx server lifecycle.",{"title":32,"path":33,"stem":34,"description":1801,"icon":35,"children":-1},"Provide additional options to customize listening server.",1784376632893]