[{"data":1,"prerenderedAt":173},["ShallowReactive",2],{"page-\u002Fguides\u002Ftrailing-slash":3},{"id":4,"title":5,"body":6,"description":165,"extension":166,"meta":167,"navigation":168,"path":169,"seo":170,"stem":171,"__hash__":172},"content\u002F4.guides\u002F23.trailing-slash.md","Trailing Slashes",{"type":7,"value":8,"toc":160},"minimark",[9,22,83,88,91,137,149],[10,11,12,13,17,18,21],"p",{},"tileserver-rs uses the trailing slash to distinguish ",[14,15,16],"strong",{},"API endpoints"," from the\n",[14,19,20],{},"built-in web viewer",". The same path with and without a trailing slash can be\ntwo different things:",[23,24,25,38],"table",{},[26,27,28],"thead",{},[29,30,31,35],"tr",{},[32,33,34],"th",{},"Path",[32,36,37],{},"What it serves",[39,40,41,53,63,73],"tbody",{},[29,42,43,50],{},[44,45,46],"td",{},[47,48,49],"code",{},"\u002Fstyles\u002Fprotomaps-light",[44,51,52],{},"Style JSON (API)",[29,54,55,60],{},[44,56,57],{},[47,58,59],{},"\u002Fstyles\u002Fprotomaps-light\u002F",[44,61,62],{},"Map viewer UI (SPA)",[29,64,65,70],{},[44,66,67],{},[47,68,69],{},"\u002Fdata\u002Fprotomaps",[44,71,72],{},"TileJSON (API)",[29,74,75,80],{},[44,76,77],{},[47,78,79],{},"\u002Fdata\u002Fprotomaps\u002F",[44,81,82],{},"Data inspector UI (SPA)",[84,85,87],"h2",{"id":86},"normalization-rules","Normalization rules",[10,89,90],{},"Every request path is normalized before routing:",[92,93,94,119],"ul",{},[95,96,97,100,101,104,105,104,108,111,112,104,115,118],"li",{},[14,98,99],{},"API paths are trimmed"," — ",[47,102,103],{},"\u002Fhealth\u002F",", ",[47,106,107],{},"\u002Fping\u002F",[47,109,110],{},"\u002Fdata.json\u002F",",\n",[47,113,114],{},"\u002Fstyles.json\u002F",[47,116,117],{},"\u002F_openapi\u002F"," and any other slashed API path serve the same\nresponse as their canonical slash-less form. Curl either; both work.",[95,120,121,124,125,128,129,132,133,136],{},[14,122,123],{},"SPA viewer paths are preserved"," — exactly two families keep their\ntrailing slash: ",[47,126,127],{},"\u002Fstyles\u002F{id}\u002F"," and ",[47,130,131],{},"\u002Fdata\u002F{id}\u002F",". These fall through to the\nembedded web UI (the ",[47,134,135],{},"frontend"," feature) instead of the greedy API routes.",[10,138,139,140,128,142,144,145,148],{},"This means \"Open in viewer\" and \"Inspect\" links from the home page\n(",[47,141,127],{},[47,143,131],{},") are stable, shareable URLs — including\nquery flags like ",[47,146,147],{},"\u002Fstyles\u002F{id}\u002F?raster"," — while every API consumer gets\ncanonical, slash-insensitive endpoints.",[150,151,153],"alert",{"type":152},"info",[10,154,155,156,159],{},"Implementation lives in ",[47,157,158],{},"crates\u002Ftileserver-rs\u002Fsrc\u002Ftrailing_slash.rs"," — a\nselective layer applied outside the router. A blanket trim would collapse the\nviewer routes onto the API routes and 404 every viewer link; the selective\nlayer exists precisely to prevent that regression.",{"title":161,"searchDepth":162,"depth":162,"links":163},"",2,[164],{"id":86,"depth":162,"text":87},"How tileserver-rs routes trailing slashes — API paths are trimmed to a canonical form, while the SPA viewer routes \u002Fstyles\u002F{id}\u002F and \u002Fdata\u002F{id}\u002F keep their slash.","md",{},true,"\u002Fguides\u002Ftrailing-slash",{"title":5,"description":165},"4.guides\u002F23.trailing-slash","3_1U82le9-ecMB4wvOjaPqLKsNZe87XtWoFayQE5Cuo",1793056239623]