KUMODeck
日本語

Hosting & deploy

kumodeck deploy <dir> publishes a folder of static files (your built web app or game) as a new version of an environment. Every version is kept; switching between them is instant. Apps that render on the server (Next.js, Astro, SvelteKit, Nuxt, React Router, TanStack Start, Hono…) deploy too: see Server-rendered apps.

Hosting is optional. Your app can live anywhere — your own server, itch.io, a CDN — and still use every other part of KUMODeck (list its origin in web.allowedOrigins if you want to stop other sites from using your publishable key). Hosted apps are served under your app's own subdomain — or your own domain — with no KUMODeck branding.

Deploy#

kumodeck deploy dist --env production     # production (deploy's default is development)
kumodeck deploy dist --env development -m "new boss fight"
kumodeck deployments                      # list versions (* = live)
kumodeck rollback 12                      # make version 12 live again, instantly
EnvironmentURL
productionhttps://<slug>.kumodeck.app/
developmenthttps://<slug>--dev.kumodeck.app/
local serverhttps://api.kumodeck.com/play/<slug>/ and https://api.kumodeck.com/play/<slug>--dev/

The folder must contain an index.html. Dotfiles and node_modules are skipped (.well-known/ is included); symlinks are not followed.

Server-rendered apps#

Apps that make their pages on the server — Next.js (with vinext or OpenNext, below), Astro (with its Cloudflare adapter), SvelteKit, Nuxt, React Router in framework mode, TanStack Start, Hono, SolidStart — deploy the same way you would to Cloudflare Workers: a Worker plus its static assets. Off by default: kumodeck deploy turns on serverRendering (and hosting, which it needs) for that environment and says so in one line, the same way a static deploy turns on hosting. To turn it on yourself: kumodeck features on serverRendering && kumodeck config push --env development (this also turns on hosting).

kumodeck deploy --env development --dry-run   # build, then check with KUMODeck without deploying
kumodeck deploy --env development             # build, upload what is new, make it live

Run it in the project folder without a folder argument. The CLI then:

  1. Detects the project: a wrangler.jsonc with main, or a server framework in package.json. Anything else — or kumodeck deploy dist, or a deployDir in kumo.json — is the static deploy above. --app / --static override it.
  2. Runs wrangler setup --yes if the project has no wrangler.jsonc yet (Cloudflare's own setup adds the adapter).
  3. Builds on your computer (npm run build, or pnpm / yarn / bun from the lockfile), then bundles with wrangler deploy --dry-run --outdir .kumo/app-build. Nothing is sent to Cloudflare by wrangler, and KUMODeck never builds your code.
  4. Asks KUMODeck for a dry run (sizes, files to upload, databases it will create, warnings), uploads only new files and switches the live version.

wrangler must be installed in the project (npm install -D wrangler); without it the CLI uploads the build as static files and says so. --json shows the detection, the build commands and what was sent.

  • Where it runs: https://<slug>.kumodeck.app/, https://<slug>--dev.kumodeck.app/ and your custom domains — not under /play/. POST requests (forms, server actions) reach the app.
  • Static or app: an environment serves whichever version is live. kumodeck rollback switches between static and app versions instantly.
  • Databases and files: D1, KV, R2 and Queue producers in wrangler.jsonc are created per environment and shared with Functions by binding name.
  • Your app's database: create the tables and query it with kumodeck db migrate DB and kumodeck db query DB "SELECT …" (the same as kumodeck functions db …). Functions do not need to be on. db migrate reads the folder from migrations_dir of that database in the app's wrangler.jsonc (default migrations). A SQL mistake answers d1_query_error with the database's message: fix the SQL.
  • Logs: what the app prints with console.log / console.error is kept for 7 days. Read it with kumodeck logs (together with Functions, mixed by time; --source app for the app only) or the MCP tool functions_logs (Logs). Lines are billed at cost like Functions logs; kumodeck deploy --no-logs keeps none for that version. A version deployed before apps had logs answers app_logs_unavailable: deploy again. Code that throws while starting answers app_script_error with the exception in details.error.
  • Secrets: kumodeck functions secret put NAME sets them for Functions and the app together. Do not put secrets in vars (the deploy warns about names that look like secrets).
  • Scheduled jobs: the app's Worker cannot have crons. Put them in Functions.
  • Cookies: KUMODeck removes Domain= from the app's Set-Cookie, so cookies stay on the app's own host.
  • Not available yet: image optimization (serve images as they are), ISR, Durable Objects, service bindings, native modules. The deploy Skill lists what to use instead.
  • Cost: requests and CPU of the app, billed at cost from your prepaid balance.

Next.js#

Next.js is built with vinext (Cloudflare's Next.js on Vite) — the recommended way — or with OpenNext (@opennextjs/cloudflare), which stays as the fallback. Both run without ISR for now. kumodeck deploy uses vinext when it is in package.json and OpenNext when only OpenNext is set up; with neither, it stops and prints the lines for both (next_adapter_choice). --next vinext / --next opennext chooses for one deploy.

vinext (recommended) — needs Node.js 22.18 or later:

  1. Install it: npm install -D vinext (or pnpm / yarn / bun add -D vinext).
  2. Run npx vinext check and change what it marks ✗.
  3. Run this once (it writes vite.config.ts and cloudflare.config.ts; the three choices are needed, or it stops and asks):

``sh npx vinext init --platform=cloudflare --cdn-cache=none --data-cache=none --image-optimization=none ``

  1. kumodeck deploy. The CLI runs vinext check and vite build (instead of steps 2–3 above) and uploads .cloudflare/output/v0. It does not change your files, and no Cloudflare account is needed.
  • If vinext check finds something vinext does not support, the deploy lists it. With OpenNext installed, it deploys with OpenNext this time; change the listed items and deploy again to use vinext. Without OpenNext, it stops (vinext_check_failed). kumodeck deploy --next vinext tries vinext anyway.
  • Image optimization (imagesOptimizer, bindings.images()) stops the deploy: vinext fails without it at runtime, so it cannot be left out. Init with --image-optimization=none; images are served as they are.
  • Keep the assets binding named ASSETS (as vinext init writes it).

OpenNext (the fallback) — for apps vinext does not support yet, or when vinext does not work. For OpenNext, step 3 above is opennextjs-cloudflare build (it runs next build); the CLI then copies the pages built at build time into the assets (.open-next/cache → .open-next/assets/cdn-cgi/_next_cache).

  1. Install @opennextjs/cloudflare and wrangler. If wrangler.jsonc or open-next.config.ts is missing, the CLI writes the smallest one it needs and says so (it never overwrites). The assets binding must be named ASSETS.
  2. An open-next.config.ts you already have must use the read-only cache from the assets. The CLI stops before building otherwise (it does not edit your files):

```ts import { defineCloudflareConfig } from "@opennextjs/cloudflare"; import staticAssetsIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/static-assets-incremental-cache";

export default defineCloudflareConfig({ incrementalCache: staticAssetsIncrementalCache }); ```

  1. Set images: { unoptimized: true } in next.config (no image optimization yet; images are served as they are).
  2. kumodeck deploy (kumodeck deploy --next opennext when vinext is installed too).
  • revalidate has no effect yet; server-rendered pages and pages built at build time work. R2 / KV / D1 caches and queues for ISR (NEXT_INC_CACHE_*, NEXT_TAG_CACHE_*, NEXT_CACHE_*) are refused.
  • services (WORKER_SELF_REFERENCE) and images in OpenNext's wrangler.jsonc are not sent; the deploy says so.
  • Edge middleware works. Node.js middleware gets a warning (it is experimental in OpenNext too); if it fails, go back to Edge.

Both: Route Handlers and Server Actions reach the app. A static export (output: "export", then kumodeck deploy out) still works. Next.js needs Node.js compatibility: it is on by default with a compatibility_date of 2026-08-04 or later (vinext init also writes nodejs_compat). With an older date, KUMODeck adds nodejs_compat and says so (next_nodejs_compat_added): add it to compatibility_flags in wrangler.jsonc too, so local runs match.

If Next.js does not work#

Next.js on KUMODeck is in beta: it has not been checked on the production runtime yet. Every Next.js deploy says so (the warning vinext_beta for vinext, next_beta for OpenNext; when a deploy fails, the error's details.fallback points here too). If the app does not work after it is deployed, deploy it again in one of these ways.

If it was deployed with vinext, deploy it with OpenNext — install OpenNext if it is not there yet, then:

npm install -D @opennextjs/cloudflare wrangler
kumodeck deploy --next opennext   # development (deploy's default)

If it still does not work, try one of these two.

1. Export it as a static site — for apps that do not need server code on each request (no Route Handlers, Server Actions, middleware or cookies read on the server). In next.config:

const nextConfig = { output: "export", images: { unoptimized: true } };
export default nextConfig;
npx next build        # writes the site to out/
kumodeck deploy out    # development (deploy's default)

Server code the app still needs can move to the project's own Functions.

2. Render each page on request — remove ISR: delete export const revalidate = … from pages and layouts and next: { revalidate: … } from fetch calls (use cache: "no-store" where the data must be fresh), and remove the NEXT_INC_CACHE_* / NEXT_TAG_CACHE_* / NEXT_CACHE_* bindings from wrangler.jsonc. Then deploy as before:

kumodeck deploy        # development (deploy's default)

If it still does not work, keep the error's code and details from kumodeck deploy --json when you report it.

Custom domains#

Serve the production app on a domain you own, such as play.mygame.com. Your users, share links and X cards (the card tags and image URLs) then use that domain — they never see a KUMODeck URL. KUMODeck handles the certificate and the ownership check; you add two DNS records. Off by default, production only.

kumodeck features on customDomains && kumodeck config push --env production
kumodeck hosting domains add play.mygame.com     # prints the DNS records to add
kumodeck hosting domains status play.mygame.com  # check again after adding them
kumodeck hosting domains                         # list
kumodeck hosting domains remove play.mygame.com  # asks first (--yes in scripts)

add prints the two records exactly as your DNS provider asks for them:

CNAME  play.mygame.com               <the CNAME target it prints>
TXT    _kumo-verify.play.mygame.com  kumo-verify=<your account's value>

The CNAME points the name at KUMODeck; the TXT proves the domain is yours (the value is the same for every domain on your account). Add both at the same time where you manage the domain (your registrar or DNS provider). KUMODeck checks every hour and starts serving once they are found — usually minutes, sometimes a few hours. Your <slug>.kumodeck.app URL keeps working, so links you already posted do not break.

  • Watch two things: status (pending → active, or failed) and verified (ownership proven by the TXT, with the time it was last confirmed). While something is missing, the output lists what to do next.
  • Keep the TXT record after the domain is live: KUMODeck re-checks it every day and stops serving on the domain if it is gone.
  • One TXT on a parent domain covers everything under it: _kumo-verify.mygame.com also proves play.mygame.com, www.mygame.com and so on, so further subdomains need only their CNAME.
  • A domain someone else merely signed up for is not theirs: whoever proves ownership with the TXT gets it. The only "already in use" refusal is when this app already uses the domain for the other purpose (Functions vs. hosting).
  • Root domains (mygame.com): some DNS providers cannot put a CNAME at the root. Use ALIAS / ANAME / CNAME flattening if your provider has it, or a subdomain such as play.mygame.com.
  • What changes when it is active: the URL returned by kumodeck deploy, share links, X card tags and image URLs use your domain (card tags you already put in <head>: get them again with kumodeck share tags --env production and replace them); sign-in redirects and web.allowedOrigins accept it automatically (https only).
  • Limits: up to 5 per project. Development stays on its --dev URL.
  • Cost: custom domains are billed daily at cost from your prepaid balance. Remove the ones you no longer use.
  • Turning customDomains off stops serving on your domains (listing and removing still work).

The dashboard (Hosting) and the MCP tools hosting_domains_list / hosting_domain_add / hosting_domain_remove do the same.

Changing the URL slug#

The slug is the name in your game's URLs (<slug>.kumodeck.app). You can change it later — once every 30 days. Undoing your last change within 24 hours does not count.

kumodeck slug                                        # current slug, next change date, old slugs and their redirects
kumodeck slug check sky-racers-2                     # free? what changes? (URLs, the two choices below)
kumodeck slug change sky-racers-2 --keep-redirect    # or --no-redirect: you must pick one
kumodeck slug redirect sky-racers on                 # turn the redirect of an old slug on (or off)

When you change it, you choose what happens to links that use the old slug — there is no default:

  • Keep old links working (--keep-redirect): old URLs redirect (301) to the new ones, keeping the path and query (so share links still count). $1 a month per old slug, charged daily from your prepaid balance while it is on. It keeps working even if your balance runs out; it stops only when you turn it off.
  • Let them stop working (--no-redirect): old URLs show a neutral "Not found" page. No charge.

Either way the old slug stays reserved for your game — nobody else can take it — so you can turn its redirect on or off later. Custom domains do not change.

Changing the slug or a redirect asks you to confirm it is you (your password, or signing in again with Google etc.). The CLI asks for your password in a terminal; accounts without a password change it in the dashboard (Hosting → URL slug). The MCP tools project_slug_get / project_slug_check only read: an AI assistant cannot change your slug.

How uploads work#

  1. The CLI hashes every file (sha256) and sends the manifest.
  2. The server answers with the hashes it does not have for this project — only those are uploaded (8 in parallel, retried).
  3. finalize + activate switch the live version atomically.

Redeploying a large app after a small change uploads only the changed files. Development and production share uploads, so promoting the same build costs nothing.

Caching#

FileCache-Control
*.htmlno-cache (revalidated every load — a deploy or rollback is visible immediately)
hashed files under assets/, static/, chunks/ (e.g. app.3f9a1c.js)immutable, 1 year
everything elsemax-age=0, must-revalidate (cheap 304s)

ETags are content hashes; Range and HEAD are supported. /x redirects to /x/ when x/index.html exists; a 404.html at the root is used for missing paths.

Security headers#

Each app is served from its own subdomain, separate from the API and the dashboard, so apps cannot read each other's storage or a developer's session. A permissive CSP keeps CDNs, eval and WebAssembly engines working while blocking plugins and insecure (http:) loads. There is no frame-ancestors rule, so you can embed your app or game on other sites such as itch.io. Development deployments send X-Robots-Tag: noindex.

Limits#

LimitValue
Files per deploy5,000
Total size500 MB
Single file50 MB
Path length512 characters
Unfinished deploys20 per project (expire after 24 h)
Server-rendered app: Worker64 MiB (uncompressed)
Server-rendered app: asset file25 MiB
Server-rendered app: per request1,000 ms CPU, 50 subrequests

CI#

Skip kumodeck login and pass a secret key; the environment comes from the key:

KUMO_API_URL=https://api.kumodeck.com KUMO_SECRET_KEY=$KUMO_SECRET_KEY npx kumodeck deploy dist

Coming soon SPA fallback routing and per-project headers (COOP/COEP for SharedArrayBuffer) are planned.