[{"data":1,"prerenderedAt":1626},["ShallowReactive",2],{"navigation":3,"-guide-middleware":56,"-guide-middleware-surround":1621},[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":1615,"extension":1616,"meta":1617,"navigation":1618,"path":28,"seo":1619,"stem":29,"__hash__":1620},"content/1.guide/4.middleware.md",{"type":59,"value":60,"toc":1605,"icon":30},"minimark",[61,66,451,455,476,486,490,497,591,596,604,682,700,714,718,741,828,834,841,1026,1062,1097,1108,1125,1175,1201,1226,1233,1257,1273,1281,1285,1305,1309,1340,1557,1578,1591,1601],[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 colored line per request with the method, URL, status, and duration.",[67,605,608],{"className":69,"code":606,"filename":607,"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,609,610,623,637,641,647,657,678],{"__ignoreMap":72},[77,611,612,614,617,619,621],{"class":79,"line":80},[77,613,84],{"class":83},[77,615,616],{"class":87}," { serve } ",[77,618,102],{"class":83},[77,620,106],{"class":105},[77,622,109],{"class":87},[77,624,625,627,630,632,635],{"class":79,"line":112},[77,626,84],{"class":83},[77,628,629],{"class":87}," { log } ",[77,631,102],{"class":83},[77,633,634],{"class":105}," \"srvx/log\"",[77,636,109],{"class":87},[77,638,639],{"class":79,"line":119},[77,640,116],{"emptyLinePlaceholder":115},[77,642,643,645],{"class":79,"line":163},[77,644,392],{"class":125},[77,646,395],{"class":87},[77,648,649,652,654],{"class":79,"line":184},[77,650,651],{"class":87},"  middleware: [",[77,653,294],{"class":125},[77,655,656],{"class":87},"()],\n",[77,658,659,661,664,666,668,670,672,675],{"class":79,"line":207},[77,660,413],{"class":125},[77,662,663],{"class":87},": () ",[77,665,157],{"class":83},[77,667,428],{"class":83},[77,669,431],{"class":125},[77,671,193],{"class":87},[77,673,674],{"class":105},"\"👋 Hello there.\"",[77,676,677],{"class":87},"),\n",[77,679,680],{"class":79,"line":216},[77,681,450],{"class":87},[67,683,687],{"className":684,"code":685,"language":686,"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,688,689],{"__ignoreMap":72},[77,690,691,694,697],{"class":79,"line":80},[77,692,693],{"class":87},"[10:32:03 AM] GET http://localhost:3000/ [200] (",[77,695,696],{"class":125},"1.42ms",[77,698,699],{"class":87},")\n",[456,701,702,703,705,706,708,709,713],{},"The duration is measured around ",[74,704,474],{},", so place ",[74,707,534],{}," first in the array for it to cover the whole chain. The ",[710,711,712],"a",{"href":13},"CLI"," enables this middleware automatically.",[592,715,717],{"id":716},"static-files","Static files",[456,719,720,600,722,724,725,728,729,732,733,736,737,740],{},[74,721,551],{},[74,723,546],{}," serves files from a directory, with ",[74,726,727],{},"index.html"," resolution, ",[74,730,731],{},".html"," extension fallback (",[74,734,735],{},"/about"," → ",[74,738,739],{},"about.html","), common MIME types, gzip/Brotli compression, and path-traversal protection.",[67,742,744],{"className":69,"code":743,"filename":607,"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,745,746,758,772,776,782,798,824],{"__ignoreMap":72},[77,747,748,750,752,754,756],{"class":79,"line":80},[77,749,84],{"class":83},[77,751,616],{"class":87},[77,753,102],{"class":83},[77,755,106],{"class":105},[77,757,109],{"class":87},[77,759,760,762,765,767,770],{"class":79,"line":112},[77,761,84],{"class":83},[77,763,764],{"class":87}," { serveStatic } ",[77,766,102],{"class":83},[77,768,769],{"class":105}," \"srvx/static\"",[77,771,109],{"class":87},[77,773,774],{"class":79,"line":119},[77,775,116],{"emptyLinePlaceholder":115},[77,777,778,780],{"class":79,"line":163},[77,779,392],{"class":125},[77,781,395],{"class":87},[77,783,784,786,789,792,795],{"class":79,"line":184},[77,785,651],{"class":87},[77,787,788],{"class":125},"serveStatic",[77,790,791],{"class":87},"({ dir: ",[77,793,794],{"class":105},"\"public\"",[77,796,797],{"class":87}," })],\n",[77,799,800,802,804,806,808,810,812,815,818,821],{"class":79,"line":207},[77,801,413],{"class":125},[77,803,663],{"class":87},[77,805,157],{"class":83},[77,807,428],{"class":83},[77,809,431],{"class":125},[77,811,193],{"class":87},[77,813,814],{"class":105},"\"Not found\"",[77,816,817],{"class":87},", { status: ",[77,819,820],{"class":169},"404",[77,822,823],{"class":87}," }),\n",[77,825,826],{"class":79,"line":216},[77,827,450],{"class":87},[456,829,830,831,833],{},"When no file matches the request, it calls ",[74,832,474],{}," — so your handler acts as the fallback for unmatched paths.",[456,835,836],{},[468,837,838,840],{},[74,839,551],{}," options:",[842,843,844,851,863,901,921,932,953,970,987,1006],"ul",{},[845,846,847,850],"li",{},[74,848,849],{},"dir",": The directory to serve files from (required).",[845,852,853,856,857,860,861,343],{},[74,854,855],{},"methods",": HTTP methods to serve (default ",[74,858,859],{},"[\"GET\", \"HEAD\"]","). Other methods fall through to ",[74,862,474],{},[845,864,865,868,869,871,872,875,876,148,879,882,883,885,886,889,890,893,894,897,898,343],{},[74,866,867],{},"dotfiles",": Dot segments (path segments starting with ",[74,870,343],{},") that may be served (default ",[74,873,874],{},"[\".well-known\"]","). A path containing any other dot segment — ",[74,877,878],{},"/.env",[74,880,881],{},"/.git/config"," — falls through to ",[74,884,474],{},". Pass ",[74,887,888],{},"true"," to serve every dot segment, or ",[74,891,892],{},"false"," (or ",[74,895,896],{},"[]",") to serve none, including ",[74,899,900],{},"/.well-known/",[845,902,903,906,907,909,910,912,913,916,917,920],{},[74,904,905],{},"encodings",": Serve precompressed variants from disk (default ",[74,908,892],{},"). Pass ",[74,911,888],{}," for ",[74,914,915],{},"{ 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,918,919],{},"stat"," that always misses.",[845,922,923,926,927,909,929,931],{},[74,924,925],{},"compress",": Compress a response on the fly when no precompressed variant is served (default ",[74,928,888],{},[74,930,892],{}," to serve only what is already on disk.",[845,933,934,937,938,941,942,945,946,949,950,952],{},[74,935,936],{},"lastModified",": Emit a ",[74,939,940],{},"Last-Modified"," header from the file's modification time, and answer a matching ",[74,943,944],{},"If-Modified-Since"," request with ",[74,947,948],{},"304 Not Modified"," (default ",[74,951,888],{},").",[845,954,955,958,959,962,963,945,966,949,968,952],{},[74,956,957],{},"etag",": Emit a weak ",[74,960,961],{},"ETag"," validator, and answer a matching ",[74,964,965],{},"If-None-Match",[74,967,948],{},[74,969,888],{},[845,971,972,975,976,979,980,949,983,986],{},[74,973,974],{},"maxAge",": Freshness lifetime in ",[468,977,978],{},"seconds",", emitted as ",[74,981,982],{},"Cache-Control: max-age=\u003Cn>",[74,984,985],{},"undefined",", no header). Lets a client reuse a response without a request until it goes stale.",[845,988,989,992,993,995,996,999,1000,1002,1003,1005],{},[74,990,991],{},"immutable",": Add the ",[74,994,991],{}," directive to ",[74,997,998],{},"Cache-Control",", so a client does not revalidate a still-fresh response even on reload (default ",[74,1001,892],{},"). Only takes effect alongside ",[74,1004,974],{},", and only makes sense for fingerprinted (content-hashed) assets.",[845,1007,1008,1011,1012,1015,1016,148,1018,1021,1022,1025],{},[74,1009,1010],{},"renderHTML",": A function receiving ",[74,1013,1014],{},"{ request, html, filename }"," for every HTML file (",[74,1017,731],{},[74,1019,1020],{},".htm","), returning the ",[74,1023,1024],{},"Response"," to send. Use it to inject or template markup before serving.",[456,1027,1028,1029,1032,1033,1036,1037,1039,1040,1042,1043,1046,1047,1050,1051,1054,1055,1058,1059,343],{},"A request resolves in order: the path itself, then ",[74,1030,1031],{},"\u003Cpath>.html",", then ",[74,1034,1035],{},"\u003Cpath>/index.html",". So ",[74,1038,735],{}," serves ",[74,1041,739],{},", while an extension-less file (",[74,1044,1045],{},"LICENSE",", an ACME challenge token) is served at its exact name. A trailing-slash request names a directory, so ",[74,1048,1049],{},"/sub/"," resolves only ",[74,1052,1053],{},"sub/index.html"," — never ",[74,1056,1057],{},"sub.html"," or a file named ",[74,1060,1061],{},"sub",[456,1063,1064,1065,1067,1068,1071,1072,148,1075,1078,1079,1082,1083,1086,1087,1090,1091,1093,1094,1096],{},"By default a compressible response is compressed on the fly as it is sent. Enabling ",[74,1066,905],{}," adds a disk lookup that takes precedence: for ",[74,1069,1070],{},"/app.js"," with ",[74,1073,1074],{},"Accept-Encoding: br",[74,1076,1077],{},"app.js.br"," is served if it exists (with ",[74,1080,1081],{},"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,1084,1085],{},"encodings: true"," plus a build step is the cheapest way to serve maximum-quality compressed assets. The two switches are independent: ",[74,1088,1089],{},"compress: false"," serves only what is on disk, and ",[74,1092,905],{}," off with ",[74,1095,925],{}," on always compresses on the fly.",[456,1098,1099,1100,1103,1104,1107],{},"Brotli compresses at quality 4 rather than the ",[74,1101,1102],{},"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,1105,1106],{},"Content-Length"," it has on disk.",[456,1109,1110,1111,1114,1115,1118,1119,1121,1122,1124],{},"Compression applies to compressible types only, so a ",[74,1112,1113],{},".br"," next to an image or font is ignored, and those responses omit ",[74,1116,1117],{},"Vary: Accept-Encoding"," — which compressible ones always set, including uncompressed ones, as a shared cache must key on the header either way. ",[74,1120,1010],{}," routes are never compressed and always read the source file: a variant on disk would not match the rendered output, and the ",[74,1123,1024],{}," the hook returns is the caller's to encode.",[456,1126,1127,1128,1130,1131,1133,1134,1136,1137,1139,1140,1142,1143,1145,1146,1152,1153,1155,1156,1159,1160,1163,1164,1167,1168,1171,1172,1174],{},"Every file served without ",[74,1129,1010],{}," carries an ",[74,1132,961],{}," and a ",[74,1135,940],{}," header, and a conditional request that still matches is answered with an empty ",[74,1138,948],{}," before the body is ever read. ",[74,1141,965],{}," takes precedence over ",[74,1144,944],{},", matching ",[710,1147,1151],{"href":1148,"rel":1149},"https://www.rfc-editor.org/rfc/rfc9110#section-13.2.2",[1150],"nofollow","RFC 9110",". The ",[74,1154,961],{}," is weak (",[74,1157,1158],{},"W/\"…\"","): it is derived from the file's size and modification time rather than its bytes, and folds in the ",[74,1161,1162],{},"Content-Encoding"," so a brotli and a gzip response under one URL get distinct validators. Pass ",[74,1165,1166],{},"etag: false"," or ",[74,1169,1170],{},"lastModified: false"," to drop either header and stop honoring its conditional. ",[74,1173,1010],{}," routes carry neither, since the rendered body is the caller's to validate.",[456,1176,1177,1179,1180,1182,1183,1185,1186,1189,1190,1193,1194,1197,1198,1200],{},[74,1178,998],{}," is opt-in and off by default, so a client revalidates with those validators on every use. Set ",[74,1181,974],{}," (in seconds) to send ",[74,1184,982],{}," and let a client reuse a response without a request until it goes stale; add ",[74,1187,1188],{},"immutable: true"," to send ",[74,1191,1192],{},"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,1195,1196],{},"304"," too, so a revalidation refreshes the stored freshness. Like the validators, it is omitted on ",[74,1199,1010],{}," routes.",[456,1202,1203,1205,1206,1211,1212,1215,1216,1218,1219,1222,1223,952],{},[74,1204,900],{}," is served by default because ",[710,1207,1210],{"href":1208,"rel":1209},"https://www.rfc-editor.org/rfc/rfc8615",[1150],"RFC 8615"," reserves it for public metadata: ACME HTTP-01 challenges and ",[74,1213,1214],{},"security.txt"," live there. Allow-listing is by exact segment name, so ",[74,1217,874],{}," serves neither a sibling sharing its prefix (",[74,1220,1221],{},".well-known-backup",") nor a dot segment nested under it (",[74,1224,1225],{},".well-known/.env",[456,1227,1228,1229,1232],{},"Text responses declare ",[74,1230,1231],{},"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.",[1234,1235,1236],"note",{},[456,1237,1238,1239,1241,1242,1244,1245,1247,1248,736,1251,882,1254,1256],{},"\nFiles are only served from within ",[74,1240,849],{},", and both rules above are re-checked against the path a symlink actually resolves to. Symlinks are followed, but one resolving outside ",[74,1243,849],{}," — or onto a dot segment ",[74,1246,867],{}," hides, such as ",[74,1249,1250],{},"public.txt",[74,1252,1253],{},".env",[74,1255,474],{}," instead of being served.",[1234,1258,1259],{},[456,1260,1261,1262,1264,1265,1268,1269,1272],{},"\nDespite the runtime-neutral name, ",[74,1263,546],{}," is ",[468,1266,1267],{},"Node-API-only"," — it uses ",[74,1270,1271],{},"node:fs"," internally, so it works only on runtimes with Node.js compatibility (Node, Deno, Bun).",[456,1274,1275,1276,1280],{},"See ",[710,1277,1279],{"href":1278},"/guide/cli#serving-static-files","Serving static files"," for the equivalent CLI flag.",[592,1282,1284],{"id":1283},"mutual-tls","Mutual TLS",[456,1286,1287,600,1292,1294,1295,1301,1302,343],{},[710,1288,1290],{"href":1289},"/guide/tls#mutual-tls-mtls",[74,1291,568],{},[74,1293,563],{}," requests a client certificate during the TLS handshake and exposes it on ",[710,1296,1298],{"href":1297},"/guide/handler#requesttls",[74,1299,1300],{},"request.tls",". It requires the ",[710,1303,1304],{"href":48},"Node.js adapter",[592,1306,1308],{"id":1307},"tracing","Tracing",[456,1310,1311,600,1313,1315,1316,1318,1319,1326,1327,1330,1331,1334,1335,343],{},[74,1312,586],{},[74,1314,581],{}," wraps your ",[74,1317,465],{}," handler and each middleware with ",[710,1320,1323],{"href":1321,"rel":1322},"https://nodejs.org/api/diagnostics_channel.html",[1150],[74,1324,1325],{},"diagnostics_channel"," instrumentation, publishing to the ",[74,1328,1329],{},"srvx.request"," and ",[74,1332,1333],{},"srvx.middleware"," ",[710,1336,1339],{"href":1337,"rel":1338},"https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel",[1150],"tracing channels",[67,1341,1343],{"className":69,"code":1342,"filename":607,"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,1344,1345,1357,1371,1385,1389,1406,1441,1473,1511,1515,1519,1525,1535,1553],{"__ignoreMap":72},[77,1346,1347,1349,1351,1353,1355],{"class":79,"line":80},[77,1348,84],{"class":83},[77,1350,616],{"class":87},[77,1352,102],{"class":83},[77,1354,106],{"class":105},[77,1356,109],{"class":87},[77,1358,1359,1361,1364,1366,1369],{"class":79,"line":112},[77,1360,84],{"class":83},[77,1362,1363],{"class":87}," { tracingPlugin } ",[77,1365,102],{"class":83},[77,1367,1368],{"class":105}," \"srvx/tracing\"",[77,1370,109],{"class":87},[77,1372,1373,1375,1378,1380,1383],{"class":79,"line":119},[77,1374,84],{"class":83},[77,1376,1377],{"class":87}," { tracingChannel } ",[77,1379,102],{"class":83},[77,1381,1382],{"class":105}," \"node:diagnostics_channel\"",[77,1384,109],{"class":87},[77,1386,1387],{"class":79,"line":163},[77,1388,116],{"emptyLinePlaceholder":115},[77,1390,1391,1394,1396,1399,1401,1404],{"class":79,"line":184},[77,1392,1393],{"class":125},"tracingChannel",[77,1395,193],{"class":87},[77,1397,1398],{"class":105},"\"srvx.request\"",[77,1400,952],{"class":87},[77,1402,1403],{"class":125},"subscribe",[77,1405,395],{"class":87},[77,1407,1408,1411,1414,1416,1419,1421,1424,1426,1428,1431,1433,1435,1437,1439],{"class":79,"line":207},[77,1409,1410],{"class":125},"  start",[77,1412,1413],{"class":87},": ({ ",[77,1415,418],{"class":144},[77,1417,1418],{"class":87}," }) ",[77,1420,157],{"class":83},[77,1422,1423],{"class":87}," console.",[77,1425,294],{"class":125},[77,1427,193],{"class":87},[77,1429,1430],{"class":105},"`[start] ${",[77,1432,418],{"class":87},[77,1434,343],{"class":105},[77,1436,356],{"class":87},[77,1438,359],{"class":105},[77,1440,677],{"class":87},[77,1442,1443,1446,1448,1450,1452,1454,1456,1458,1460,1463,1465,1467,1469,1471],{"class":79,"line":216},[77,1444,1445],{"class":125},"  asyncEnd",[77,1447,1413],{"class":87},[77,1449,418],{"class":144},[77,1451,1418],{"class":87},[77,1453,157],{"class":83},[77,1455,1423],{"class":87},[77,1457,294],{"class":125},[77,1459,193],{"class":87},[77,1461,1462],{"class":105},"`[end] ${",[77,1464,418],{"class":87},[77,1466,343],{"class":105},[77,1468,356],{"class":87},[77,1470,359],{"class":105},[77,1472,677],{"class":87},[77,1474,1475,1478,1480,1482,1484,1487,1489,1491,1493,1495,1497,1500,1502,1504,1506,1508],{"class":79,"line":222},[77,1476,1477],{"class":125},"  error",[77,1479,1413],{"class":87},[77,1481,418],{"class":144},[77,1483,148],{"class":87},[77,1485,1486],{"class":144},"error",[77,1488,1418],{"class":87},[77,1490,157],{"class":83},[77,1492,1423],{"class":87},[77,1494,1486],{"class":125},[77,1496,193],{"class":87},[77,1498,1499],{"class":105},"`[error] ${",[77,1501,418],{"class":87},[77,1503,343],{"class":105},[77,1505,356],{"class":87},[77,1507,359],{"class":105},[77,1509,1510],{"class":87},", error),\n",[77,1512,1513],{"class":79,"line":227},[77,1514,450],{"class":87},[77,1516,1517],{"class":79,"line":253},[77,1518,116],{"emptyLinePlaceholder":115},[77,1520,1521,1523],{"class":79,"line":274},[77,1522,392],{"class":125},[77,1524,395],{"class":87},[77,1526,1527,1530,1533],{"class":79,"line":282},[77,1528,1529],{"class":87},"  plugins: [",[77,1531,1532],{"class":125},"tracingPlugin",[77,1534,656],{"class":87},[77,1536,1537,1539,1541,1543,1545,1547,1549,1551],{"class":79,"line":288},[77,1538,413],{"class":125},[77,1540,663],{"class":87},[77,1542,157],{"class":83},[77,1544,428],{"class":83},[77,1546,431],{"class":125},[77,1548,193],{"class":87},[77,1550,674],{"class":105},[77,1552,677],{"class":87},[77,1554,1555],{"class":79,"line":304},[77,1556,450],{"class":87},[456,1558,1559,1560,1563,1564,1567,1568,1570,1571,1167,1574,1577],{},"Each event carries ",[74,1561,1562],{},"{ server, request }",", plus ",[74,1565,1566],{},"{ middleware: { index, handler } }"," on the ",[74,1569,1333],{}," channel. Pass ",[74,1572,1573],{},"{ fetch: false }",[74,1575,1576],{},"{ middleware: false }"," to instrument only one of the two.",[456,1579,1580,1581,1583,1584,1587,1588,1590],{},"Because plugins run in order, ",[74,1582,586],{}," only wraps middleware registered before it — keep it ",[468,1585,1586],{},"last"," in the ",[74,1589,481],{}," array so it covers middleware added by earlier plugins.",[1592,1593,1594],"important",{},[456,1595,1596,1264,1598,343],{},[74,1597,581],{},[468,1599,1600],{},"experimental",[1602,1603,1604],"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":1606},[1607,1608,1609],{"id":64,"depth":112,"text":65},{"id":453,"depth":112,"text":454},{"id":488,"depth":112,"text":489,"children":1610},[1611,1612,1613,1614],{"id":594,"depth":119,"text":595},{"id":716,"depth":119,"text":717},{"id":1283,"depth":119,"text":1284},{"id":1307,"depth":119,"text":1308},"Plugins and middleware allow adding reusable server extensions.","md",{"icon":30},{"icon":30},{"title":27,"description":1615},"NEBGeoFyXicyrgsOrKFsrgFqq_9mf9A_ZkzJfiICgPo",[1622,1624],{"title":22,"path":23,"stem":24,"description":1623,"icon":25,"children":-1},"Control srvx server lifecycle.",{"title":32,"path":33,"stem":34,"description":1625,"icon":35,"children":-1},"Provide additional options to customize listening server.",1784305566158]