Site layout
A release separates visitor-facing files from private server code. This page lists the directories, what is reachable from the web, the reserved names, and the import rules.
Layout
site/
public/ static files, served as is
index.html
styles.css
404.html optional not-found page (or set errorPages in the manifest)
errors/ optional static error pages named in errorPages
assets/logo.svg
api/ handlers; /api/<name> runs api/<name>.js
contact.js
_admin/ private extension handlers (never public)
jobs/ scheduled and background job scripts (never public; see jobs.md)
lib/ shared modules imported by handlers
html.js
private/
migrations/ SQL migrations, 0001_name.sql ...
microhost.json optional manifest (or microhost.config.js)
Only public/ is a web root. A visitor requests /styles.css, not /public/styles.css. Everything outside public/ is the private namespace: not served, importable by handlers. The folder names api, lib and private are conventions (only api/ has routing meaning through /api/<name>, and only private/migrations/ has meaning for migrations); you can place handler scripts anywhere outside public/ and route to them from the manifest.
What is reachable
- Static files come from
public/only, in every release.static.rootin the manifest does not change this today (Manifest). A path ending in/maps toindex.htmlin that directory. - Paths with a segment starting with
., andmicrohost.json, are never served and answer a generic 404 (they also cannot be script routes). First segment_adminis rejected before routing. - Symlinks are never followed; a symlink below
public/makes the file invisible, and ZIP uploads containing symlinks are rejected. - Headers of static responses:
Content-Typefrom the file extension (HTML, CSS, JS, JSON, SVG, common images, fonts,wasm,pdf, media; anything else isapplication/octet-stream); a strongETag;Cache-Control: no-cachefor HTML,public, max-age=31536000, immutablefor files whose name carries an 8+ hex-digit content hash (for exampleapp.3f9a2b1c.js), otherwisepublic, max-age=0, must-revalidate; conditional requests (304) and byte ranges. - Static files accept
GETandHEAD. - Handlers are reachable only through a route: an explicit manifest route or
/api/<name>. See Routing. Server source (.js,.mjs,.cjs) underapi/orlib/is never served as a static file. - Static assets are not writable through
ctx.storage; storage is private data. Publishing new static files means creating a new release.
Reserved names
The server owns resolved-manifest.json, resolved-manifest.meta.json and db-schema.json at the release root. An upload that contains any of them is rejected. microhost.config.js and microhost.json are manifest sources, not served.
Imports
Handlers are ES modules and use static imports:
import { escapeHtml } from "../lib/html.js";
import { tasks } from "microhost/db";
- Relative only: a specifier must start with
./or../and end in.js. Resolution is relative to the importing file. - Inside the release, outside
public/. A relative import that leaves the release is refused with a capability error (HTTP 403), and so is any import of apublic/file (public/files are static assets, not modules). - No bare specifiers other than the four reserved SDK names
microhost/sql,microhost/db(anddb),microhost/auth,microhost/session. There is no npm resolution at runtime. If you need a library, bundle it into one plain.jsfile first, and only if it sticks to the supported APIs (Handler API). - No dynamic
import(),eval,new Function. A dynamic import fails the request with HTTP 500. - No URL imports, no JSON or other non-
.jsimports. - Limits per execution: at most 256 modules and 8 MiB of module source.
Module-level code runs on every request (each execution has a fresh runtime). Do not keep persistent state in module variables.
Browser code versus server code
public/app.js runs in the visitor's browser with the DOM and the browser's own fetch. Server handlers do not have those. A handler cannot be imported by browser code, and browser code cannot read anything outside public/.
Packaging
For a ZIP upload, the archive root must contain public/, api/, microhost.json or microhost.config.js directly, not inside a wrapping directory. The ingest validates entry names, rejects symlinks, special files, encrypted entries, duplicates and case or Unicode-normalization collisions. Default ingest limits: 200 MiB extracted, 10,000 files, depth 16, compression ratio 100, 60 seconds; the admin API accepts a ZIP upload of 32 MiB by default. See Deploying.