Production Build

koyoapp build turns your Koyo app into a folder of plain HTML, ready for any static host.

Why a static build#

Koyo is static-first. Every page is a Python function that renders to HTML, so the whole site can be evaluated once, ahead of time, into real files. The result needs no Python at serving time and can be dropped onto any CDN, object store, or web server.

What the build produces#

.koyo/build/site/
  index.html            # the / route
  styles/koyo.css       # compiled, minified CSS
  htmx.min.js           # vendored script, served from public/
  ...                   # public/ copied verbatim
  • Every static route is written as <route>/index.html, so relative asset links keep working on any host.
  • public/ is copied into the site root unchanged.
  • The compiled Tailwind stylesheet is minified for production.
  • Python bytecode cache is never written into the build output (.koyo/pycache keeps it out).

Dynamic routes#

Routes with a bracketed dynamic segment, such as app/blog/[slug]/page.py, have no list of concrete URLs to prerender by default. The build skips them and prints a message about each skipped route:

Skipped dynamic routes, they are not prerendered and must be served by the running Koyo server: blog/[slug]

Static pages, fragments, and the site index all build normally. A dynamic route's listing page (for example app/blog/page.py at /blog) is still a static route and is built.

Sessions and personalization#

During the build there is no session and request is None. use_state falls back to its initial value, so built pages render fresh defaults, never a visitor's private state. Personalization happens only while the served pages run with a live request.

Serving the build#

Point any static host at .koyo/build/site. For a quick local check:

python3 -m http.server 8000 --directory .koyo/build/site
# open http://127.0.0.1:8000

Because output is plain HTML with relative paths, there is no required server rewrite for the routes the build created.