YaDominiosYaDominios

Publishing on YaDominios Cloud: complete technical guide (for developers and AI)

Updated: 2026-07-31 · YaDominios

YaDominios Cloud publishes a GitHub repository as a live site at <name>.sitios.dev, with free SSL, on our global network (330+ cities). You connect it at yapanel.yadominios.com/panel/cloud. You start FREE and with no card: the Preview plan publishes one site at <name>.sitios.dev so you can show it to your client before charging anything; it includes no database and no custom domain, and both arrive when you move to a paid plan. There are three project types: (a) static site, which only needs an index.html; (b) pre-built app, with a _worker.js at the root; and (c) a project that needs building (Next.js, Astro, Vite), which you CANNOT push as source code: a GitHub Action builds it and leaves the output on the yapanel-build branch, and that is the branch you connect. Inside your code you get env.DB (the site's own database) and env.BUCKET (storage). The database is queried over HTTP at POST yapanel.yadominios.com/api/hosting/db/query using the database token from the dashboard. Every push to the connected branch republishes on its own. This page is the complete technical reference; an AI can read it and get an app published with no human help. It includes the hand-off protocol for AI assistants: when the site is done, give the human - in chat, in a copyable block - the repository URL and the branch, then wait for the result (the published link, or the error, which comes tagged as belonging to either the repository or the platform).

What it is (mental model in 20 seconds)

YaDominios Cloud is serverless hosting: you connect a public GitHub repository from the dashboard (yapanel.yadominios.com/panel/cloud), pick a name, and the site goes live at <name>.sitios.dev with automatic HTTPS, in 330+ cities worldwide. We don't run your build: we deploy what's in the repo. Your repo must carry the final result (static HTML or a compiled worker). Every git push republishes on its own, in seconds.

Your repo GitHub git push YaDominios Cloud builds and publishes seconds yourname.sitios.dev live · HTTPS · global network
Connect once; after that every git push publishes on its own.

Start free: publish first, charge later

You don't need a plan, a domain, or a card to publish. The Preview plan is free and exists for one very specific situation in this trade: showing the page to your client before they pay. You publish, you send them the link on WhatsApp from the dashboard itself, and when they say yes you connect their domain and move to a paid plan.

Preview (free)Paid plans (from $3/mo)
Live link with HTTPSYes, at <name>.sitios.devYes
Sites1From 1 to unlimited, depending on the plan
Storage (env.BUCKET)512 MBFrom 5 GB
Database (env.DB)NoYes
Your own domainNoYes, with SSL

So if your project needs a database, it has to be on a paid plan: on Preview env.DB does not exist and no database token is issued. Everything else in this guide works the same either way.

If you're an AI and you just built the site: how to hand it over

This part is addressed to the assistant that wrote the code (Claude, ChatGPT, Cursor, Lovable, whichever). Your job doesn't end when the project compiles. It ends when the person who asked for it has in hand the two pieces of data the dashboard is going to ask for, and knows what to do with whatever comes back. Almost nobody does this, which is why the human ends up staring at a screen with no idea what to paste where.

Step 1 — Push the project to GitHub, public

Private repositories: these can be published now. Your repository has to be authorized in the YaDominios read key — write to us with the repository name and we'll enable it. If yours is public, there's nothing to do.

The repository can be public (we download it without asking anyone for credentials) or private and already authorized. Push the output, not the unbuilt source (see "The three project types" above): an index.html, an already-bundled _worker.js, or the branch your GitHub Action produces.

Step 2 — Give the human the repository and the branch, in a copyable block

It's exactly two pieces of data, no more. Don't send a .zip, don't ask them to clone anything, don't ask for Cloudflare credentials (they're never needed). Write it out like this, ready to paste into yapanel.yadominios.com/panel/cloud:

The site is on GitHub. To publish it:

1. Go to yapanel.yadominios.com/panel/cloud
2. Give it a short name (it goes live as name.sitios.dev)
3. Paste these two values:

   Repository: https://github.com/USER/PROJECT
   Branch: main

4. Hit "Publish for free"

When it finishes, send me the link you get - or the message
in red, if one shows up - and I'll take it from there.

If your project needs building, the branch is not main: it's the one your Action leaves behind (by convention yapanel-build). Say so plainly in the same message, because this is the single most common mistake.

Step 3 — Wait for the result. They'll come back with one of three things

Publishing takes up to a minute and the dashboard shows six steps live. What they bring back is one of these three, and each one calls for a different response:

What they send youWhat it meansWhat you do
A name.sitios.dev linkIt's published and answering.Open it, check it looks right, and confirm it's done.
An error tagged "This gets fixed in your repository"The failure is in the project, not the platform.Fix it, git push to the same branch, and say so: nothing needs reconnecting, every push republishes on its own.
An error tagged "This one is on us, not on you"The platform failed; the project is fine.Don't change the code. Have them retry in a minute and, if it persists, write to support.

The failures you'll see, and what to fix

MessageWhat happenedFix
"Couldn't find ... on branch ..."The repository is private, the name is wrong, or that branch doesn't exist.Make it public or correct the branch.
"This repo is a project that needs building"You pushed source code (Next.js, Vite, Astro), not the finished page.Push the built output, or set up the Action that produces the built branch.
"index.html missing at the root"There's no home page in any of the folders we look at.Leave an index.html at the root or in dist/, build/, out/, public/, docs/.
"Cloudflare rejected the package"Your _worker.js won't start: not an ES module, or it uses Durable Objects or Queues.Bundle it as an ES module with export default { fetch } and drop those dependencies.

One rule that saves a lot of back-and-forth: if the dashboard says the failure is ours, it's ours. Don't start rewriting a project that's fine - that's the fastest way to break something that was working.

Mode 1 — Static site

Single requirement: an index.html. We look for it at the repo root or in public/, dist/, build/, site/, _site/, docs/, or out/ (the first folder that has it is the published root). Every file in that folder is served as an edge-cached asset; HTML is always revalidated (max-age=0), so changes show instantly.

What does not get published: anything starting with a dot stays out (.gitignore, .github/, .vscode/, .claude/). Those are your workshop notes and have no business sitting on your client's page. The one exception is .well-known/, which is published because that's where domain verifications and indexing keys live.

Mode 2 — App with a backend (_worker.js)

If the published root carries a _worker.js file, the site is an APP: that file runs as a worker on the YaDominios Cloud server and receives ALL requests. It must be a single, already-compiled ES-module JavaScript file (bundle your dependencies with esbuild/rollup) with this shape:

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    if (url.pathname.startsWith("/media/")) {
      // your backend here (env.DB, env.BUCKET)
      return Response.json({ ok: true });
    }
    return env.ASSETS.fetch(request); // everything else: your static files
  }
};

⚠️ Don't use /api/ for your backend routes. Static files are served before your worker; in apps that ship assets (Next.js/OpenNext) the /api/* prefix can be captured by the file router and return 404 without reaching your code. Use another prefix (/media, /upload, /data…). It's a real platform detail, verified in production.

Node.js: you don't need to configure anything to use Node APIs (node:stream, node:crypto, etc.). Every app with a backend runs with Node compat enabled automatically by YaDominios Cloud, with a recent compatibility date. That's why frameworks (Next.js with OpenNext, etc.) work with no tweaks.

When you publish an app, we automatically provision (idempotent, zero setup):

BindingWhat it isHow to use it
env.DBThe site's own SQL database (serverless SQLite engine). Created as site-<name>-db.await env.DB.prepare("SELECT * FROM t WHERE id=?").bind(1).all()
env.BUCKETThe site's own file and image storage (bucket site-<name>).await env.BUCKET.put("photo.jpg", bytes) · await env.BUCKET.get("photo.jpg")
env.ASSETSYour static files from the repo.return env.ASSETS.fetch(request)
your variablesThe environment variables you saved in the dashboard (secrets included).env.STRIPE_KEY, env.API_URL

Note on env.BUCKET: the binding and permissions are already on the platform. If R2 isn't enabled on the account at publish time, the app ships anyway (without storage) and the bucket connects automatically on the next deploy once it's enabled. Program against env.BUCKET as usual.

Tables: schema.sql

If the published root carries schema.sql, we run it against the site's database on every publish. Write idempotent DDL:

CREATE TABLE IF NOT EXISTS customers (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  created_at TEXT DEFAULT (datetime('now'))
);

_worker.js and schema.sql aren't served to the public: they're code/config.

The three project types

Before publishing, figure out which one you have. Everything else depends on this.

TypeWhat the repo must containNeeds a build?
a) Static siteindex.html at the root or inside dist/, build/, out/, public/, site/, _site/ or docs/No
b) Pre-built appA _worker.js at the rootNo (you already built it)
c) Project that needs building
Next.js, Astro, Vite, Nuxt…
The build output, on the yapanel-build branchYes

⚠️ Case (c) is where everyone gets stuck: you cannot push source code and expect it to work. YaDominios Cloud does not run your build: it publishes whatever is on the connected branch. Push the source of a Next.js app and nothing comes out.

Projects that need building

The fix is a GitHub Action that builds on its own and leaves the output on a separate branch called yapanel-build. In the dashboard you connect that branch, not main.

Next.js: the size trap

Raw compiler output cannot be deployed. When you build a Next.js app with OpenNext, the .open-next folder ends up with over 1,000 files and around 19 MB. That does not go up as-is.

It has to be bundled into a single file before publishing. The GitHub Action we provide already does this with wrangler. If you build by hand, you cannot skip that bundling step: without it, the deploy fails.

The official adapter is OpenNext (@opennextjs/cloudflare). Here is the full Action:

# .github/workflows/build.yml
name: build-for-yadominios-cloud
on: { push: { branches: [main] } }
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci && npx opennextjs-cloudflare build
      # IMPORTANT: .open-next/worker.js is NOT standalone (it imports ./cloudflare/,
      # ./middleware/, etc.). It must be bundled into ONE file.
      # WRANGLER does the bundling, not raw esbuild: wrangler applies the correct
      # Workers runtime rules (node:*, workerd conditions). A hand-rolled esbuild
      # call either fails to compile or crashes on startup.
      # --dry-run touches NO Cloudflare account: it only writes the file.
      - run: |
          npx wrangler deploy --dry-run --outdir=.dist-worker --minify
          mkdir out-deploy
          cp .dist-worker/worker.js out-deploy/_worker.js
          cp -r .open-next/assets/* out-deploy/ 2>/dev/null || true
          cp yadominios.json out-deploy/ 2>/dev/null || true
      - uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_branch: yapanel-build
          publish_dir: ./out-deploy

The wrangler step needs two things in your repo: wrangler as a devDependency, and a wrangler.jsonc at the root with at least this (the name doesn't matter — with --dry-run nothing is ever deployed to Cloudflare):

// wrangler.jsonc
{
  "name": "my-site",
  "main": ".open-next/worker.js",
  "compatibility_date": "2026-07-01",
  "compatibility_flags": ["nodejs_compat"],
  "assets": { "directory": ".open-next/assets" }
}

Next.js with OpenNext: you don't have to do anything

OpenNext adds three Durable Object classes on its own — DOQueueHandler, DOShardedTagCache and BucketCachePurge — even if your app never uses them. YaDominios Cloud doesn't offer Durable Objects yet, and for a while that kept perfectly good packages from publishing.

Not anymore: we strip them at publish time, and we tell you so in the "Package checked" step of the log. You don't need to touch your config or write your own cleaner.

If you'd rather avoid them at the source, in your open-next.config.ts:

// open-next.config.ts
import { defineCloudflareConfig } from "@opennextjs/cloudflare";

export default defineCloudflareConfig({
  incrementalCache: undefined,
  queue: undefined,
  tagCache: undefined,
});

An honest warning: depending on the OpenNext version, that config does not always keep them out of the bundle — we found this out in a real integration. That's why we do the cleaning ourselves instead of trusting your config. To check it yourself:

grep -oE "DOQueueHandler|DOShardedTagCache|BucketCachePurge" .open-next/worker.js | sort -u

(The -E matters: without it the pipe is a literal character and the command reports "clean" every time, even with all three classes inside.)

What you lose without them: background revalidation (ISR). Pages render on request and are served from the edge cache — plenty for a store or a company site.

If your app uses its OWN Durable Objects (chat rooms, live counters, real time), that we do reject, and we say so: we can't strip those without breaking your app. They aren't available yet.

Other app-mode limits: no queues either. The final worker must be a single ES-module file whose export default has fetch; if your bundler emits CommonJS (module.exports), set the output format to esm. The normal size of a bundled Next.js app (3–6 MB) is not a problem.

If something fails, the panel tells you which step

Publishing is six steps and you watch them one by one, LIVE, on the site screen: repository foundbranch downloadedpackage checkeddatabase and storage readysite publishedverified live. The last step is the guarantee: the platform VISITS your freshly published page and only marks green if it actually answers — "published" never means "stored but unreachable". If something breaks, the failing step turns red with the exact reason, and the later ones stay gray — they didn't fail, they never got to run.

Publish and republish

  1. First time: dashboard → yapanel.yadominios.com/panel/cloud → site name + repo URL + branch → “Publish my site.” Name rules: lowercase, numbers and hyphens, max 63, no -- and no reserved names (www, api, admin, docs…).
  2. Your site goes live at <name>.sitios.dev, with SSL, free, forever.
  3. After that: every git push to the connected branch republishes the site on its own and clears the cache, so the change shows up immediately.

Only the connected branch republishes (and that's on purpose)

Pushes to other branches — say main on a project that needs building — do not republish. This isn't an oversight: the build takes a couple of minutes, so if main republished, it would deploy the previous build (the one already sitting there), not the change you just made. You'd see your change as “published” while actually looking at the old one.

That's why, on projects that need building, the connected branch is yapanel-build — the one the Action writes only after the build finishes.

If publishing fails

The reason is written in the dashboard: your site's card → “Error log.” If an AI is helping you, copy that text verbatim and paste it: it carries the path, the method, and the full stack trace.

And if a version can't be published, we say so. We retry it for you a few times — a passing hiccup on GitHub clears itself — but we don't spin in place: after the third attempt we stop and write it in your site's log, in red. Your site does not go down in the meantime: it stays online on the last version that published successfully. We get the alert at the same moment you do.

Publishing and connecting a domain are two separate things

You don't need a domain to publish. The natural order is:

  1. You publish → your page is already live at <name>.sitios.dev and you can share it.
  2. Then, if you want, you connect your own domain from the site's card.

They're independent flows. The .sitios.dev subdomain never goes away, even after you connect your own domain.

Database API (console and migrations)

You can query your site's database over HTTP, without opening the dashboard. This is what lets an AI or a script create tables, seed data, and run queries.

1. Get your database token

Dashboard → YaDominios Cloud → your site's card → “View token” button.

It is shown only ONCE. Copy it and store it somewhere safe. If you lose it, generate a new one from the same button: the previous token stops working immediately.

The token opens only THAT site's database. It grants no access to any other site or to your account.

2. Make the request

POST https://yapanel.yadominios.com/api/hosting/db/query
Content-Type: application/json

{
  "sitio": "your-site-name",
  "token": "<your database token>",
  "sql": "select * from orders where id = ?",
  "params": [1]
}

Note: the field is spelled sitio (Spanish for “site”) — that is the exact key the API expects.

3. Response

{
  "results": [ { "id": 1, "total": 250 } ],
  "rowsRead": 1,
  "rowsWritten": 0
}

Rules

  • SQL is always parameterized: put ? placeholders inside "sql" and the values in "params", in the same order. Never concatenate values into the SQL string — that is how injections happen.
  • If your query takes no values, send "params": [].
  • For migrations, send your CREATE TABLE / ALTER TABLE in "sql".

Errors

CodeWhat it means
401The token is not valid (wrong token, or you regenerated it and are still using the old one).
400A body field is missing, or the SQL itself failed.

Full example (JavaScript)

const r = await fetch("https://yapanel.yadominios.com/api/hosting/db/query", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    sitio: "my-store",
    token: process.env.YADOMINIOS_DB_TOKEN,
    sql: "insert into orders (customer, total) values (?, ?)",
    params: ["Ana", 250]
  })
});
const data = await r.json();
console.log(data.rowsWritten); // 1

Platform configuration (yadominios.json)

Always set compatibility_date next to nodejs_compat: that flag requires a date of 2024-09-23 or later, and without it the behavior is left to the platform's default. For variables, KV, Durable Objects, queues, cron, and compatibility flags, add a yadominios.json at the published root (we also accept wrangler.jsonc, the format AIs already generate). We read that file and provision/wire everything automatically on YaDominios Cloud, isolated per site.

{
  "compatibility_date": "2026-07-01",
  "compatibility_flags": ["nodejs_compat"],
  "vars": { "API_URL": "https://api.yourservice.com" },
  "kv_namespaces": [{ "binding": "CACHE" }],
  "durable_objects": { "bindings": [{ "name": "ROOM", "class_name": "Room" }] },
  "migrations": [{ "new_sqlite_classes": ["Room"] }],
  "queues": { "producers": [{ "binding": "QUEUE", "queue": "jobs" }] },
  "triggers": { "crons": ["*/10 * * * *"] }
}
CapabilityHow you declare itHow you use it in your code
Public variablesvars in the file (they live in the repo)env.API_URL
SECRET variables (API keys)In the dashboard → your site → Environment variables (NOT in the repo)env.STRIPE_KEY
KV (cache/key-value)kv_namespacesawait env.CACHE.get("k")
Durable Objects (state, real time) — not available yetdurable_objects + migrationsNot provisioned yet: a package exporting Durable Object classes is rejected at publish time.
Queuesqueues.producers (requires Queues enabled on the account)await env.QUEUE.send(msg)
Crontriggers.cronsYour worker responds to GET /__scheduled (we invoke it on your cron)
Compatibility flagscompatibility_flags

Secret variables: never put them in the repo. They go in the dashboard (stored encrypted server-side and injected as secrets at deploy; the dashboard shows only the names, never the values). When you save them, the site is re-published to apply them.

Cron (scheduled tasks): declare the expression in triggers.crons and expose a GET /__scheduled route in your worker with your task. Our scheduler invokes it at the right minute (it arrives with the x-yad-cron header). With OpenNext/Next, add that route as one more endpoint in your worker.

Custom domain (yourbusiness.com)

Órbita plans and up include a custom domain. It's self-service from the dashboard: YaDominios Cloud → your site → “Connect my own domain.” You type your domain and the dashboard shows you 2 unique nameservers; you paste them at your registrar (each has its own copy box) and within minutes to hours your domain serves your site with automatic HTTPS. Your name.sitios.dev subdomain keeps working forever.

Error log (observability)

If your app throws an exception in production, the visitor sees a friendly error page and the error is recorded with its full stack under YaDominios Cloud → your site → “Error log” (last 20). If an AI runs your site, copy the error verbatim and paste it in: it has the path, the method, and the trace to fix it.

How to check your change actually landed (read this before calling a deploy broken)

Do not search for an exact phrase inside the HTML. Frameworks split text across elements, so a literal search fails even when the change IS published.

Real example: you want to confirm the text “Your spot among the 100” shipped. In the published HTML it arrives like this:

<p>Your spot among the <span>100</span></p>

A grep "Your spot among the 100" returns nothing, and you'd conclude the deploy failed. It didn't: the text is there, split across two elements.

What to do instead:

  • Search for a short, continuous fragment that doesn't cross tags ("spot among the").
  • Or open it in a browser and look at it — that's the definitive check.
  • Or compare the “last published” timestamp in the dashboard against the time of your push.

Checklist for an AI publishing here

  1. Identify the project type (static / pre-built _worker.js / needs building). If it needs building, do not try to push source code: it will not work.
  2. Static? → make sure index.html is at the root or inside dist/ build/ out/ public/ site/ _site/ docs/, and push.
  3. Pre-built app with a backend? → _worker.js at the root (single bundle, export default { fetch }), use env.DB / env.BUCKET / env.ASSETS, add schema.sql if you need tables.
  4. Needs building (Next.js, Astro, Vite…)? → add the GitHub Action, confirm the yapanel-build branch was created, and connect that branch in the dashboard. On Next.js the bundling step is mandatory: raw .open-next (1,000+ files, ~19 MB) does not deploy.
  5. Connect the repo at yapanel.yadominios.com/panel/cloud (once). Every push to the connected branch publishes on its own.
  6. Data? → get the database token via “View token” and use POST https://yapanel.yadominios.com/api/hosting/db/query with parameterized SQL.
  7. When verifying, don't grep for literal phrases in the HTML (see above). If something failed, read “Error log” on the site's card.

Frequently asked questions

Do you run my npm run build?

No. We deploy what's in the repo: static HTML or an already-compiled _worker.js. The build is automated with a GitHub Action in your own repo.

Can I use plain Node.js (Express)?

Not directly: the backend runs as a worker (export default { fetch }). Express doesn't apply; use the worker pattern or a framework with an adapter (Next.js via OpenNext, Hono, etc.).

Does the repo have to be public?

Yes for now: we connect public GitHub repositories. The files are read via the GitHub API on publish.

What are the database limits?

It's a serverless SQLite database: ideal for small and medium apps. Each site has its own isolated database, with automatic backup.

How do I connect my own domain (mydomain.com)?

Órbita plans and up include a custom domain: it's self-service from the dashboard (your site → Connect my own domain). The name.sitios.dev subdomain stays available forever.

Keep reading

Ready for your domain?

Search for it now and have it live in minutes.

Search my domain