[{"data":1,"prerenderedAt":230},["ShallowReactive",2],{"blog:page:\u002Fen\u002Fblog\u002Fstatic-deployment":3,"blog:articles:en":172,"blog:translations:\u002Fen\u002Fblog\u002Fstatic-deployment":227},{"id":4,"title":5,"author":6,"body":7,"cover":157,"coverAlt":158,"date":159,"description":160,"extension":161,"featured":162,"meta":163,"navigation":164,"path":165,"seo":166,"stem":167,"tags":168,"__hash__":171},"blog\u002Fen\u002Fblog\u002F4.static-deployment\u002Findex.md","Deploying a blog as static assets","John Martin",{"type":8,"value":9,"toc":152},"minimark",[10,14,18,23,77,80,115,119,126,130,133,136,148],[11,12,5],"h1",{"id":13},"deploying-a-blog-as-static-assets",[15,16,17],"p",{},"The deployment of this blog has no code in it. The Worker configuration\ndeclares a directory of files, a 404 page, and nothing else.",[19,20,22],"h2",{"id":21},"what-the-config-says","What the config says",[24,25,30],"pre",{"className":26,"code":27,"language":28,"meta":29,"style":29},"language-jsonc shiki shiki-themes github-light github-dark","{\n  \"assets\": {\n    \"directory\": \".output\u002Fpublic\",\n    \"not_found_handling\": \"404-page\",\n    \"html_handling\": \"drop-trailing-slash\",\n  },\n}\n","jsonc","",[31,32,33,41,47,53,59,65,71],"code",{"__ignoreMap":29},[34,35,38],"span",{"class":36,"line":37},"line",1,[34,39,40],{},"{\n",[34,42,44],{"class":36,"line":43},2,[34,45,46],{},"  \"assets\": {\n",[34,48,50],{"class":36,"line":49},3,[34,51,52],{},"    \"directory\": \".output\u002Fpublic\",\n",[34,54,56],{"class":36,"line":55},4,[34,57,58],{},"    \"not_found_handling\": \"404-page\",\n",[34,60,62],{"class":36,"line":61},5,[34,63,64],{},"    \"html_handling\": \"drop-trailing-slash\",\n",[34,66,68],{"class":36,"line":67},6,[34,69,70],{},"  },\n",[34,72,74],{"class":36,"line":73},7,[34,75,76],{},"}\n",[15,78,79],{},"Three decisions worth explaining:",[81,82,83,94,103],"ul",{},[84,85,86,93],"li",{},[87,88,89,90],"strong",{},"No ",[31,91,92],{},"main"," - with no Worker script, nothing executes per request. The\nplatform serves files directly, which is both the cheapest and the most\nreliable serving mode.",[84,95,96,102],{},[87,97,98,101],{},[31,99,100],{},"404-page"," handling"," - an unknown URL gets a real 404 status with a real\nerror page. SPA-style fallbacks return 200 for everything, which quietly\nturns typos into indexable \"pages\".",[84,104,105,110,111,114],{},[87,106,107],{},[31,108,109],{},"drop-trailing-slash"," - canonical URLs here have no trailing slash.\nThe default ",[31,112,113],{},"auto-trailing-slash"," mode would redirect the canonical address\nto a non-canonical one, permanently.",[19,116,118],{"id":117},"the-404-is-part-of-the-product","The 404 is part of the product",[15,120,121,122,125],{},"A blog that answers every unknown slug with a soft 200 trains readers to\ndistrust its links. Serving a real 404 for ",[31,123,124],{},"\u002Fen\u002Fblog\u002Fnot-an-article"," is the\ndeployment doing its job.",[19,127,129],{"id":128},"identity-without-a-script","Identity without a script",[15,131,132],{},"The deployment still carries a Worker identity for the platform - previews and\nenvironment routing key off it - but the identity is not an executable. It is\na name for a directory of files, which is exactly what this surface is.",[134,135],"hr",{},[15,137,138],{},[139,140,141,142,147],"em",{},"Reading path first, deployment second: ",[143,144,146],"a",{"href":145},"\u002Fen\u002Fblog\u002Fbuild-time-content","content resolved at build\ntime"," explains why there is no runtime database to\nbind.",[149,150,151],"style",{},"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":29,"searchDepth":43,"depth":43,"links":153},[154,155,156],{"id":21,"depth":43,"text":22},{"id":117,"depth":43,"text":118},{"id":128,"depth":43,"text":129},"\u002Fassets\u002Fcover-static-deployment.svg","Cover illustration: a folder of HTML and SVG files flowing onto a globe of CDN edge nodes","2026-09-23","No Worker script, no database binding - the whole deployment is a directory of files and a 404 page.","md",false,{},true,"\u002Fen\u002Fblog\u002Fstatic-deployment",{"title":5,"description":160},"en\u002Fblog\u002F4.static-deployment\u002Findex",[169,170],"deployment","cloudflare","mWBbTiuPi2MKEjdVf2X2X3FfVl1EDanCbf3cSYnQcHU",[173,181,193,196,208,216],{"path":174,"stem":175,"title":176,"description":177,"date":178,"author":6,"tags":179,"featured":162},"\u002Fen\u002Fblog\u002Fdemo-content-note","en\u002Fblog\u002F6.demo-content-note\u002Findex","What this blog is (and is not)","This is a demo vertical slice - real layout, real pipelines, deliberately editorial content.","2026-10-07",[180],"meta",{"path":182,"stem":183,"title":184,"description":185,"date":186,"author":6,"tags":187,"featured":162,"cover":190},"\u002Fen\u002Fblog\u002Freading-first-design","en\u002Fblog\u002F5.reading-first-design\u002Findex","A reading page is not a documentation page","Why the blog article layout deliberately differs from the docs layout - hierarchy, width, and restraint.","2026-09-30",[188,189],"design","typography",{"src":191,"alt":192},"\u002Fassets\u002Fcover-reading-first.svg","Cover illustration: a narrow single-column article layout chosen over a cluttered multi-column layout",{"path":165,"stem":167,"title":5,"description":160,"date":159,"author":6,"tags":194,"featured":162,"cover":195},[169,170],{"src":157,"alt":158},{"path":197,"stem":198,"title":199,"description":200,"date":201,"author":6,"tags":202,"featured":162,"cover":205},"\u002Fen\u002Fblog\u002Flocale-switching","en\u002Fblog\u002F3.locale-switching\u002Findex","Locale switching that never lies","A language switcher should only offer what exists. Here is how the link list is derived from real content.","2026-09-16",[203,204],"i18n","ux",{"src":206,"alt":207},"\u002Fassets\u002Ftwo-locales-one-page.svg","Cover illustration: two locale paths joining into one shared page layout",{"path":145,"stem":209,"title":210,"description":211,"date":212,"author":6,"tags":213,"featured":162},"en\u002Fblog\u002F2.build-time-content\u002Findex","Content is resolved at build time","Why this blog ships as prerendered files instead of a content API - and what that buys.","2026-09-09",[214,215],"architecture","static-sites",{"path":217,"stem":218,"title":219,"description":220,"date":221,"author":6,"tags":222,"featured":164,"cover":224},"\u002Fen\u002Fblog\u002Furl-model","en\u002Fblog\u002F1.url-model\u002Findex","One URL model for every public page","How \u002Flocale\u002Fmount\u002Fpath keeps every public page predictable - and why we enforce it instead of hoping for it.","2026-09-02",[214,223],"urls",{"src":225,"alt":226},"\u002Fassets\u002Fcover-url-model.svg","Cover illustration: a single address bar splitting into locale, mount and path segments",[228,229],"en","vi",1791809694150]