Serve a ZIP as static files
Upload a ZIP bundle of HTML, CSS, JS, and images — serve it as if it were real files on your domain. No per-file upload. No folder structure to manage.
app:mount("/")
That's it. If your ZIP contains index.html and style.css:
/→ servesindex.html/style.css→ servesstyle.css
Files that don't exist in the ZIP fall through to your routes.
Mount at a sub-path
app:mount("/assets/")
-- /assets/logo.png → serves logo.png from ZIP
-- /api/users → handled by your routes
Custom handler for cache control
Set cache headers for specific file types:
app:mount("/assets/", function(ctx, items, path)
if path:match("%.css$") or path:match("%.js$") then
ctx:set_header("Cache-Control", "public, max-age=604800") -- 7 days
end
if path:match("%.png$") or path:match("%.jpg$") then
ctx:set_header("Cache-Control", "public, max-age=2592000") -- 30 days
end
return path
end)
Mount a specific ZIP by hash
app:mount("/theme/", nil, "abc123def456789...")
This lets you version your assets — upload a new ZIP, change the hash, instant rollback.
Combining with routes
Serve a React/Vue frontend from ZIP, and an API from Lua:
local app = framework()
-- Frontend (from ZIP)
app:mount("/")
-- API (from Lua)
app:group("/api/v1", function()
app:get("/users", listUsers)
app:post("/users", createUser)
end)
-- SPA fallback: any unmatched URL serves index.html
app:get("*", function(ctx)
return ctx:render("index", {})
end)
Mount multiple ZIPs
app:mount("/") -- main site from one ZIP
app:mount("/blog/", nil, "hash_for_blog_theme")
app:mount("/docs/", nil, "hash_for_docs_theme")
Each prefix gets its own ZIP. Requests fall through in registration order.
Note: Static files from ZIP are served from memory — no disk I/O. Fast and efficient.
Next: Configuration — debug mode, logging, env vars.