---
title: "Hosting de frontend (Sites)"
description: "Cómo Plurabase Sites sirve un frontend estático precompilado junto a su backend: reglas del ZIP, archivo de configuración, rollback y límites actuales."
slug: "hosting-sites"
locale: "es"
group: "platform"
order: 2
last_reviewed: 2026-10-20
---

# Hosting de frontend (Sites)

> Estado: Disponible en el piloto (sites.zip)

Un Site es el frontend estático de un proyecto. Subes un ZIP precompilado en la consola, lo publicas y Plurabase lo sirve con un archivo de configuración generado que lo conecta con el backend del proyecto. Publicar o revertir un Site nunca cambia la base de datos. En el piloto, el operador habilita Sites para cada proyecto. Los estados se definen en la [página de datos](/es/docs/facts/#estados).

## Publicar una versión

1. Compila el frontend en archivos estáticos con `index.html` en la raíz.
2. Comprime el contenido de la salida del build (no la carpeta que lo contiene).
3. En la consola, abre **Sites** (`/platform/sites/`), elige el proyecto y crea el Site.
4. Sube el ZIP y elige **Publicar**. La consola vuelve a leer por HTTP los bytes y la configuración publicados antes de confirmar.
5. Para volver atrás, elige una versión anterior y pulsa **Volver a esta versión**.

## Conectar el frontend con su backend

Cada versión se sirve con `./__simplybase/config.json`, que contiene `api_url` y `publishable_key` (la clave anon), además de `project_ref`, `release_id` y `version` (el número de formato del archivo), nunca una clave secreta:

```ts
import { createClient } from '@supabase/supabase-js'

export async function connect() {
  const reply = await fetch('./__simplybase/config.json', {
    credentials: 'omit',
    cache: 'no-store',
    redirect: 'error',
  })
  if (!reply.ok) throw new Error('Project connection unavailable')
  const config = await reply.json()
  return createClient(config.api_url, config.publishable_key, {
    auth: { persistSession: false, detectSessionInUrl: false },
    global: {
      fetch: (input, init) => fetch(input, { ...init, credentials: 'omit' }),
    },
  })
}
```

Llama a `connect()` una vez al arrancar y conserva el cliente que devuelve. El código va dentro de una función porque el único build comprobado es un bundle IIFE de esbuild, que rechaza el `await` de nivel superior.

## Reglas del ZIP

- `index.html` en la raíz y nombres de archivo ASCII. Tipos de archivo admitidos: `.html`, `.css`, `.js`, `.mjs`, `.json`, `.map`, `.webmanifest`, `.txt`, `.xml`, `.svg`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.avif`, `.ico`, `.woff`, `.woff2`, `.ttf`, `.otf` y `.wasm`.
- Cada versión se sirve bajo su propia ruta, `/__sites/r/<release>/`, y las rutas fuera de ella devuelven un error. Por eso las URLs de tus propios archivos en HTML, CSS y JavaScript deben ser relativas (`./assets/app.js`, `../logo.svg`); una URL relativa a la raíz como `/assets/app.js` falla.
- La Content Security Policy carga scripts, hojas de estilo y fuentes solo desde archivos de la versión. Están bloqueados los elementos `<script>` y `<style>` en línea, los atributos `style="…"`, los manejadores de eventos en línea, `eval` y `new Function`, las fuentes `data:`, la compilación de WebAssembly y las CDNs de terceros. Las imágenes pueden ser archivos o URLs `data:`. Empaqueta todas las dependencias, incluido `@supabase/supabase-js`.
- La página corre en un sandbox sin envío de formularios: un `<form>` nunca dispara su evento `submit`, así que inicia las acciones desde el `click` de un botón y la tecla Enter.
- El sandbox también bloquea `alert`, `confirm` y `prompt` (`confirm` devuelve `false` al instante, así que pide la confirmación con un diálogo dentro de la página), las ventanas emergentes y los enlaces en pestaña nueva (`window.open`, `target="_blank"`) y las descargas (`<a download>`).
- Las peticiones del navegador (`fetch`, XHR, WebSocket) solo llegan a la versión y a la API del proyecto, nunca a una API de terceros. Están bloqueados los iframes, el audio y el vídeo, los web workers y los elementos `<style>` que librerías de CSS-in-JS como emotion y styled-components añaden en tiempo de ejecución.
- Sin renderizado en servidor, paso de build, instalación de paquetes ni cabeceras de respuesta propias.
- Sin fallback de SPA: un enlace directo a una ruta del cliente devuelve 404. Usa rutas con hash o enlaza a `index.html`.
- El código del Site corre en un origen opaco, así que no puede usar cookies, `localStorage` ni service workers. Los usuarios vuelven a iniciar sesión al recargar.

## Builds de frameworks

Sites se ha comprobado con un solo build: el starter de la consola, un bundle de script clásico (esbuild, formato IIFE) y una hoja de estilo, ambos referenciados como `./assets/…`, con `@supabase/supabase-js` empaquetado. Juzga cualquier otra salida con las reglas de arriba:

| Build | En Sites | Motivo |
| --- | --- | --- |
| Bundle de esbuild referenciado con rutas `./`, como el starter | Comprobado | Script clásico, hoja de estilo externa, URLs relativas |
| App de una sola página con Vite (React, Vue, Svelte) | Sin comprobar | Necesita `base: './'`, `build.assetsInlineLimit: 0` (para que las fuentes pequeñas sigan siendo archivos) y no usar `@vitejs/plugin-legacy` (añade scripts en línea). Su salida `<script type="module">` todavía no se ha ejecutado en Sites. |
| Plantillas de Vue compiladas en el navegador | Bloqueado | El compilador en tiempo de ejecución usa `new Function`; los componentes de un solo archivo precompilados no |
| Salida estática de Astro | No soportado | URLs `/_astro/` relativas a la raíz, hojas de estilo pequeñas en línea por defecto, islas iniciadas con scripts en línea |
| Next.js con `output: 'export'` | No soportado | Scripts de hidratación en línea y URLs `/_next/` relativas a la raíz |

Revisa el directorio del build antes de subirlo, porque cada Site acepta dos subidas durante toda su vida, incluidas las fallidas. Ningún comando debe imprimir nada (cambia `dist` por tu directorio de salida; el último omite los bloques de datos JSON como `<script type="application/json">`, que están permitidos):

```sh
grep -rnoE '(src|href)=[^.a-zA-Z0-9]?/' dist --include='*.html'   # URLs relativas a la raíz
grep -rnoE 'url\([^.a-zA-Z0-9]?/' dist --include='*.css'          # URLs relativas a la raíz en CSS
grep -rnoE '<style|style=' dist --include='*.html'                # estilos en línea
grep -rnoE '<script[^>]*>' dist --include='*.html' | grep -v 'src=' | grep -vE 'type="application/(ld\+)?json"'   # scripts en línea
```

## Límites

- ZIP de 4 MiB, 16 MiB descomprimido.
- Dos subidas por Site durante toda su vida, incluidas las fallidas, y dos versiones retenidas.
- Pausar el proyecto también detiene su Site; las versiones siguen siendo válidas al reanudar.

## Lo que todavía no está disponible

> Estado: En desarrollo (sites.every-project)

Sites para cada proyecto (ADR-0037) está en el repositorio y no se ha ejecutado en un host. Da al Site de cada proyecto su propio origen del navegador en lugar de una ruta bajo el host de la API del proyecto:

- En la red privada, un Site recibe su propio puerto en el nombre de Tailscale del piloto. Conserva el sandbox y las reglas de arriba, porque un puerto comparte el nombre de host y las cookies de la consola, y ahí solo las organizaciones del propio operador tienen Sites. Cualquier página web que el navegador del operador abra en la red privada puede leer esos Sites, archivo de configuración incluido: no pongas en ellos nada que no publicarías.
- En un dominio público de Sites, un Site recibe su propio nombre de host (`s-<id del site>.<dominio de sites>`). Ahí desaparece el sandbox: la página puede usar `localStorage`, sus propias cookies, formularios, diálogos, ventanas emergentes y descargas. Los scripts se cargan desde el Site y desde elementos `<script>` en línea cuyo hash se calcula al servirlos; los estilos pueden ir en línea. La versión vigente se sirve en `/`, la query string se ignora al buscar el archivo (así funcionan las redirecciones de inicio de sesión con `?code=`) y una ruta sin extensión vuelve a `index.html`.
- Lo que sigue bloqueado en un Site público: los manejadores de eventos en línea (`onclick="…"`) y las URLs `javascript:`, `eval`, `new Function` y la compilación de WebAssembly, los service workers y que otra página lo muestre en un frame.
- Los scripts en línea reciben hash solo cuando la página se lee sin ambigüedad. Una página de más de 1 MiB, con más de 64 elementos `<script>` (cuentan también los externos), con un `<script>` dentro de un comentario HTML, o con `<!--` o `<script` dentro del texto de un script no recibe ningún hash, así que no se ejecuta ninguno de sus scripts en línea. Mueve ese código a archivos `.js`.
- El owner define una lista de hasta 16 orígenes https para llamadas a APIs, scripts, estilos, fuentes, medios, frames y destinos de formularios, con los comandos `get_site_policy` y `set_site_policy`. La consola todavía no tiene una pantalla para eso.
- Los límites de subida vienen del plan de la organización, como máximo un ZIP de 4 MiB y 16 MiB descomprimido, con un número diario de subidas y publicaciones en lugar de dos subidas en toda la vida del Site.
- Los Sites públicos aún no llegan a la API de su proyecto: las APIs de proyecto solo están en la red privada, así que ahí no se entrega el archivo de configuración.
- Mientras el dominio de Sites no esté en la Public Suffix List, y en navegadores cuya copia de esa lista sea anterior a la inclusión, todos los Sites que viven en él comparten un dominio registrable: cualquier Site puede leer y sobrescribir una cookie que otro Site fije con `Domain=`. Hasta esa inclusión, solo las organizaciones del propio operador tienen Sites públicos. Usa cookies `__Host-` o `localStorage`, nunca una cookie con `Domain=`.
- Un Site se sigue sirviendo mientras el backend de su proyecto está en pausa o ocupado con una operación de ciclo de vida. Una clave del proyecto rotada lo detiene hasta que se vuelva a publicar.

El edge público (certificados TLS y DNS) está apagado hasta que el dueño apruebe la exposición pública.

> Estado: En desarrollo (sites.git)

> Estado: En desarrollo (sites.domains)

> Estado: Planificado (sites.previews)

Los despliegues desde GitHub están en desarrollo: conexión mediante una GitHub App, una receta de build revisada, builds en una máquina virtual aislada y publicación automática en cada push. Los dominios propios están en desarrollo: el código para servirlos existe y el edge público está apagado. Las vistas previas están planificadas. Durante el piloto, los Sites se sirven en un nombre de host privado de Tailscale.

> Estado: Planificado (access.public-domain)

El diseño público previsto da a los frontends de clientes su propio dominio registrable (`s-<site>.plurabase.app`), separado de la consola (`app.plurabase.com`) y de las APIs de proyecto (`<project_ref>.api.plurabase.com`), para que el JavaScript del cliente nunca pueda leer cookies de la consola. Ninguno de esos dominios sirve tráfico todavía.
