What we support
aft.page is a file host plus a permission layer. It does
not run npm run build. You (or your
agent) detect the project, build locally if needed, then upload
ready files.
| Kind | What to ship | Live |
|---|---|---|
| Plain HTML | One file via Drop / MCP deploy |
hello.aft.page |
| Multi-file static | HTML + CSS + JS | share-checklist |
| Vite / React / Vue | npm run build → dist/ only |
vite-hello.aft.page |
| CRA / Rsbuild | npm run build → build/ or dist/ |
— |
| Next.js static export | output: 'export' → out/ |
— |
| Next SSR / Worker | OpenNext (or Worker) + upstream |
next-hello.aft.page |
Look at package.json, vite.config.*,
next.config.*, index.html. Plain HTML →
upload those files. Bundler app → build, then upload the output
folder only. Never upload src/,
node_modules, or .next/.
Deploy
Four ways. Same result: https://{slug}.aft.page.
Drop (humans)
aft.page/drop — folder or zip. Include
index.html.
README-only (no index.html): / is 404;
the file is at /README.md. Same shape as a Vercel
Drop of just a readme —
readme-black-chi.vercel.app
is that, not a web app.
Agent Plugin
npx plugins add vaibhavmule/aft.page — then ask
Deploy this to aft.page.
/plugins.
Agent MCP
Remote: https://mcp.aft.page/mcp
{
"mcpServers": {
"aft-page": { "url": "https://mcp.aft.page/mcp" }
}
}
Ask: Deploy this to aft.page. Tool schemas: /mcp · /mcp.md. Cursor: /with/cursor/.
API
curl -X POST https://api.aft.page/v1/deploy \
-H 'Content-Type: text/html' \
-H 'X-Aft-Client: curl' \
--data '<h1>Hello from aft.page</h1>'
Multi-file: JSON { "files": [{ "path", "content" }] }
or multipart. Optional ?slug=vite-hello. Collision
gets a suffix — never overwrites.
Logs
Owner and editors: project page → Logs, or
GET /v1/sites/{slug}/logs. Document hits, errors, and
failed deploys. No IP addresses. Kept 7 days.
Source
Owner, editors, and viewers: project page →
Source, or
GET /v1/sites/{slug}/files. File list for a deploy;
click a text file to preview. Not a public URL — live
*.aft.page/{path} is unchanged.
Secrets / environment
MCP does not set secrets. Claim the site first, then save env on the project page or API. Values are encrypted at rest; only names are listed.
-
Open
https://aft.page/claim?slug=…&token=…(deployclaimUrl) or Claim on the live URL. - Sign in → the site appears under Projects.
- Secrets panel: name + value → Save. Or use the API:
# names only
curl https://api.aft.page/v1/sites/{slug}/secrets -H "Cookie: …"
curl -X PUT https://api.aft.page/v1/sites/{slug}/secrets/ANTHROPIC_API_KEY \
-H "Content-Type: application/json" -H "Cookie: …" \
-d '{"value":"sk-…"}'
Declare intended names in aft.json so
approve-on-deploy can show them:
{
"name": "my-app",
"runtime": "next",
"capabilities": {
"secrets": ["ANTHROPIC_API_KEY"],
"egress": ["api.anthropic.com"]
}
}
Secrets are not baked into static HTML. A Vite SPA that needs a public API URL should bake it at build time; keep private keys in the vault for worker / next runtimes.
Custom domain
Invite-only during beta. Claim the site → project
Domains → request access. Once approved, add
app.example.com, then at your DNS host:
CNAME app.example.com → cname.aft.page
HTTPS issues after DNS is live (progress on the same tab). Apex
(example.com) needs ALIAS / ANAME / CNAME flattening
to the same target. The *.aft.page URL stays. Private
sign-in still uses that subdomain.
Limits
| Static | worker / next | |
|---|---|---|
| Files | 200 | 200 |
| Per file | 10 MB | 10 MB |
| Total | 50 MB | 50 MB |
Always include index.html for static sites.
Examples
- hello.aft.page — plain HTML
- vite-hello.aft.page — React + Vite
dist/ - next-hello.aft.page — Next via upstream
- share-checklist.aft.page — static multi-file
Source: examples/ in the aft.page repo.