[{"data":1,"prerenderedAt":436},["ShallowReactive",2],{"page-\u002Fdevelopment\u002Ffrontend-dev":3},{"id":4,"title":5,"body":6,"description":429,"extension":430,"meta":431,"navigation":349,"path":432,"seo":433,"stem":434,"__hash__":435},"content\u002F6.development\u002F2.frontend-dev.md","Frontend development",{"type":7,"value":8,"toc":423},"minimark",[9,22,38,43,117,126,130,146,191,194,233,242,246,257,324,378,396,400,419],[10,11,12,13,17,18,21],"p",{},"The Nuxt client in ",[14,15,16],"code",{},"apps\u002Fclient\u002F"," runs as a SPA (",[14,19,20],{},"ssr: false","). In production\nit is built to static assets that are embedded in the Rust binary and served\nsame-origin — no proxy is involved.",[10,23,24,25,29,30,33,34,37],{},"In ",[26,27,28],"strong",{},"development"," (",[14,31,32],{},"pnpm dev:client",") the Nuxt dev server runs on\n",[14,35,36],{},"http:\u002F\u002Flocalhost:3001"," and proxies API + tile + admin requests to the Rust\nbackend running on a separate port. The proxy table is dev-only.",[39,40,42],"h2",{"id":41},"default-ports","Default ports",[44,45,46,62],"table",{},[47,48,49],"thead",{},[50,51,52,56,59],"tr",{},[53,54,55],"th",{},"Concern",[53,57,58],{},"Default",[53,60,61],{},"Source",[63,64,65,81,100],"tbody",{},[50,66,67,71,76],{},[68,69,70],"td",{},"Nuxt dev server",[68,72,73],{},[14,74,75],{},":3001",[68,77,78,80],{},[14,79,32],{}," script",[50,82,83,86,91],{},[68,84,85],{},"Public Rust backend",[68,87,88],{},[14,89,90],{},":8080",[68,92,93,96,97],{},[14,94,95],{},"[server].port"," in ",[14,98,99],{},"config.toml",[50,101,102,105,110],{},[68,103,104],{},"Admin Rust backend",[68,106,107],{},[14,108,109],{},":8081",[68,111,112,96,115],{},[14,113,114],{},"[server].admin_bind",[14,116,99],{},[10,118,119,120,122,123,125],{},"The Nuxt dev proxy hardcodes ",[14,121,90],{}," and ",[14,124,109],{}," as its default targets.",[39,127,129],{"id":128},"overriding-the-backend-ports","Overriding the backend ports",[10,131,132,133,136,137,139,140,142,143,145],{},"If you run tileserver-rs on non-default ports — for example because ",[14,134,135],{},"8080"," is\nalready in use, or because you set custom ",[14,138,95],{}," and\n",[14,141,114],{}," values in your ",[14,144,99],{}," — export two env vars\nbefore starting the dev server:",[147,148,153],"pre",{"className":149,"code":150,"language":151,"meta":152,"style":152},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","TILESERVER_BACKEND_PORT=9000 \\\nTILESERVER_ADMIN_PORT=9001 \\\n  pnpm dev:client\n","bash","",[14,154,155,176,182],{"__ignoreMap":152},[156,157,160,164,168,172],"span",{"class":158,"line":159},"line",1,[156,161,163],{"class":162},"sTEyZ","TILESERVER_BACKEND_PORT",[156,165,167],{"class":166},"sMK4o","=",[156,169,171],{"class":170},"sfazB","9000",[156,173,175],{"class":174},"sBMFI"," \\\n",[156,177,179],{"class":158,"line":178},2,[156,180,181],{"class":162},"TILESERVER_ADMIN_PORT=9001 \\\n",[156,183,185,188],{"class":158,"line":184},3,[156,186,187],{"class":170},"  pnpm",[156,189,190],{"class":170}," dev:client\n",[10,192,193],{},"The Vite dev proxy reads these once at config load and rewrites every\nproxied target accordingly.",[195,196,198],"alert",{"type":197},"note",[10,199,200,201,204,205,208,209,212,213,220,221,224,225,228,229,232],{},"These env vars are deliberately ",[26,202,203],{},"not"," prefixed with ",[14,206,207],{},"NUXT_",". The ",[14,210,211],{},"NUXT_*","\nprefix is ",[214,215,219],"a",{"href":216,"rel":217},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fguide\u002Fgoing-further\u002Fruntime-config#environment-variables",[218],"nofollow","reserved by Nuxt","\nfor env vars that auto-map into ",[14,222,223],{},"runtimeConfig",". The proxy block in\n",[14,226,227],{},"nuxt.config.ts"," runs at config-load time (before any runtime exists), so\nit reads ",[14,230,231],{},"process.env"," directly and uses the unprefixed name.",[195,234,236],{"type":235},"info",[10,237,238,239,241],{},"This only affects local development. Production builds embed the SPA inside\nthe Rust binary, so the browser talks to whatever port the operator configures\nin ",[14,240,99],{}," without any proxy layer.",[39,243,245],{"id":244},"concrete-example","Concrete example",[10,247,248,249,252,253,256],{},"Run the backend on ",[14,250,251],{},":9000"," (public) and ",[14,254,255],{},":9001"," (admin):",[147,258,262],{"className":259,"code":260,"language":261,"meta":152,"style":152},"language-toml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# my-config.toml\n[server]\nhost = \"127.0.0.1\"\nport = 9000\nadmin_bind = \"127.0.0.1:9001\"\n","toml",[14,263,264,270,281,297,309],{"__ignoreMap":152},[156,265,266],{"class":158,"line":159},[156,267,269],{"class":268},"sHwdD","# my-config.toml\n",[156,271,272,275,278],{"class":158,"line":178},[156,273,274],{"class":166},"[",[156,276,277],{"class":174},"server",[156,279,280],{"class":166},"]\n",[156,282,283,286,288,291,294],{"class":158,"line":184},[156,284,285],{"class":162},"host ",[156,287,167],{"class":166},[156,289,290],{"class":166}," \"",[156,292,293],{"class":170},"127.0.0.1",[156,295,296],{"class":166},"\"\n",[156,298,300,303,305],{"class":158,"line":299},4,[156,301,302],{"class":162},"port ",[156,304,167],{"class":166},[156,306,308],{"class":307},"sbssI"," 9000\n",[156,310,312,315,317,319,322],{"class":158,"line":311},5,[156,313,314],{"class":162},"admin_bind ",[156,316,167],{"class":166},[156,318,290],{"class":166},[156,320,321],{"class":170},"127.0.0.1:9001",[156,323,296],{"class":166},[147,325,327],{"className":149,"code":326,"language":151,"meta":152,"style":152},"# Terminal 1 — backend\ntileserver-rs --config my-config.toml\n\n# Terminal 2 — frontend\nTILESERVER_BACKEND_PORT=9000 \\\nTILESERVER_ADMIN_PORT=9001 \\\n  pnpm dev:client\n",[14,328,329,334,345,351,356,366,371],{"__ignoreMap":152},[156,330,331],{"class":158,"line":159},[156,332,333],{"class":268},"# Terminal 1 — backend\n",[156,335,336,339,342],{"class":158,"line":178},[156,337,338],{"class":174},"tileserver-rs",[156,340,341],{"class":170}," --config",[156,343,344],{"class":170}," my-config.toml\n",[156,346,347],{"class":158,"line":184},[156,348,350],{"emptyLinePlaceholder":349},true,"\n",[156,352,353],{"class":158,"line":299},[156,354,355],{"class":268},"# Terminal 2 — frontend\n",[156,357,358,360,362,364],{"class":158,"line":311},[156,359,163],{"class":162},[156,361,167],{"class":166},[156,363,171],{"class":170},[156,365,175],{"class":174},[156,367,369],{"class":158,"line":368},6,[156,370,181],{"class":162},[156,372,374,376],{"class":158,"line":373},7,[156,375,187],{"class":170},[156,377,190],{"class":170},[10,379,380,381,383,384,387,388,391,392,395],{},"The frontend at ",[14,382,36],{}," will now proxy ",[14,385,386],{},"\u002Fdata\u002F*",",\n",[14,389,390],{},"\u002Fstyles\u002F*",", ",[14,393,394],{},"\u002F__admin\u002F*",", etc. to the right backend ports.",[39,397,399],{"id":398},"why-two-ports","Why two ports?",[10,401,402,403,391,406,409,410,413,414,418],{},"The admin server runs on a separate bind so it can be firewalled to localhost\n(or a private subnet) while the public tile server stays internet-facing.\nThe MCP admin UI (",[14,404,405],{},"\u002Fadmin\u002Fmcp\u002Fconnected-apps",[14,407,408],{},"\u002Fadmin\u002Fmcp\u002Fdevices",") talks to\n",[14,411,412],{},"\u002F__admin\u002Foauth\u002F*"," endpoints that only exist on the admin port. See the\n",[214,415,417],{"href":416},"\u002Fguides\u002Fmcp","MCP guide"," for the security model.",[420,421,422],"style",{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":152,"searchDepth":178,"depth":178,"links":424},[425,426,427,428],{"id":41,"depth":178,"text":42},{"id":128,"depth":178,"text":129},{"id":244,"depth":178,"text":245},{"id":398,"depth":178,"text":399},"Run the Nuxt client against the Rust backend with custom ports","md",{},"\u002Fdevelopment\u002Ffrontend-dev",{"title":5,"description":429},"6.development\u002F2.frontend-dev","BDcjzU4d4o1km2OHL1MGcOJFE63hLx9I9BBXpV3oCLM",1794150612965]