Despliegue

Una compilación de sitelo son archivos estáticos y ya: sitelo build escribe HTML, CSS y JS en dist/. Sirve cualquier hosting estático — las configuraciones de abajo solo asumen npm run build → dist/.

Las URLs limpias son directorios con index.html (/about/index.html → /about), así que las URLs bonitas funcionan de entrada, sin reglas de redirección. El 404.html se emite automáticamente: es la convención que entienden Netlify, Cloudflare Pages y GitHub Pages.

Hay versiones listas para copiar de todo esto en el ejemplo básico (examples/basic en el repositorio).

Hosting estático

Estos servicios compilan el sitio y sirven dist/ desde su propia CDN.

Netlify · Vercel · Cloudflare · AWS Amplify · AWS S3 + CloudFront · Azure Static Web Apps · Firebase Hosting · GitHub Pages · GitLab Pages · Render · DigitalOcean App Platform

Netlify

[build]
  command = "npm run build"
  publish = "dist"

Vercel

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "framework": null,
  "buildCommand": "npm run build",
  "outputDirectory": "dist",
  "installCommand": "npm install"
}

Cloudflare

Cloudflare recomienda ahora Workers en lugar de Pages para proyectos nuevos. Con esta configuración, npx wrangler deploy compila el sitio y sube dist/, y si conectas el repositorio en el panel de Cloudflare se ejecuta el mismo comando en cada push. html_handling: "drop-trailing-slash" sirve /about sin redirección, y las URL desconocidas reciben 404.html.

// Cloudflare Workers
{
  "name": "my-site",
  "compatibility_date": "2026-10-01",
  "build": {
    "command": "npm run build"
  },
  "assets": {
    "directory": "./dist",
    "not_found_handling": "404-page",
    "html_handling": "drop-trailing-slash"
  }
}

¿Sigues en Pages? Pon npm run build como comando de compilación y dist como directorio de salida, o sube una compilación local con npx wrangler pages deploy dist.

AWS Amplify

# Alojamiento en AWS Amplify
version: 1
frontend:
  phases:
    preBuild:
      commands:
        - npm install
    build:
      commands:
        - npm run build
  artifacts:
    baseDirectory: dist
    files:
      - '**/*'
  cache:
    paths:
      - node_modules/**/*

AWS S3 + CloudFront

Mantén el bucket privado y pon CloudFront delante con origin access control. CloudFront solo asigna / a index.html, así que asocia esta función a las solicitudes de visor de la distribución para servir las URL limpias de sitelo.

function handler(event) {
  var request = event.request
  var uri = request.uri

  if (uri.endsWith('/')) {
    request.uri += 'index.html'
  } else if (uri.slice(uri.lastIndexOf('/') + 1).indexOf('.') === -1) {
    request.uri += '/index.html'
  }

  return request
}

Un bucket privado responde con 403 cuando falta un archivo, así que añade respuestas de error personalizadas para 403 y 404 que devuelvan /404.html con estado 404. Después sube cada compilación y vacía la caché:

npm run build
aws s3 sync dist/ s3://my-bucket --delete
aws cloudfront create-invalidation --distribution-id MY_DISTRIBUTION_ID --paths "/*"

Azure Static Web Apps

Crea la Static Web App en el portal de Azure con / como ubicación de la app y dist como ubicación de salida; Azure añade al repositorio un workflow de GitHub Actions que ejecuta npm run build. Pon este archivo en public/ para que la compilación lo copie a dist/: sin él, Azure responde a las URL desconocidas con su propia página de error en lugar de 404.html.

{
  "responseOverrides": {
    "404": {
      "rewrite": "/404.html"
    }
  }
}

Firebase Hosting

En Firebase Hosting, elige un proyecto con firebase use --add y ejecuta firebase deploy: el hook predeploy compila primero. Las URL desconocidas reciben 404.html automáticamente, y trailingSlash: false sirve /about sin redirección.

{
  "hosting": {
    "public": "dist",
    "predeploy": "npm run build",
    "trailingSlash": false
  }
}

GitHub Pages

# GitHub Pages
name: Deploy
on:
  push:
    branches: [main]
permissions:
  contents: read
  pages: write
  id-token: write
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: github-pages
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
      - run: npm ci && npm run build
      - uses: actions/upload-pages-artifact@v5
        with:
          path: dist
      - uses: actions/deploy-pages@v5

¿Despliegas bajo una subruta (user.github.io/repo)? Compila con --base /repo/.

GitLab Pages

GitLab sirve 404.html y las URL de directorio por sí solo, así que el job solo compila y publica dist/.

# GitLab Pages
deploy-pages:
  image: node:24
  script:
    - npm ci
    - npm run build
  pages:
    publish: dist
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Render

Añade el Blueprint de abajo al repositorio, o crea un sitio estático desde el panel con el mismo comando de compilación y directorio de publicación. Las URL limpias y 404.html funcionan sin reglas de redirección.

# Render
services:
  - type: web
    name: my-site
    runtime: static
    buildCommand: npm install && npm run build
    staticPublishPath: ./dist

DigitalOcean App Platform

Pon tu repositorio en github.repo y crea la app con doctl apps create --spec .do/app.yaml, o pega la especificación en el panel. App Platform resuelve las URL de directorio y sirve 404.html por defecto.

# DigitalOcean App Platform
name: my-site
static_sites:
  - name: site
    github:
      repo: your-name/your-site
      branch: main
      deploy_on_push: true
    build_command: npm run build
    output_dir: dist

Hosts de contenedores

Estos servicios ejecutan un contenedor, y un solo Dockerfile sirve para todos: Node compila el sitio y Caddy sirve dist/ con URL limpias y responde a las desconocidas con 404.html. Caddy escucha en PORT cuando el servicio lo define, y en el puerto 80 si no. Añade los tres archivos a la raíz del proyecto.

Railway · Fly.io · Heroku · Google Cloud Run

FROM node:24-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM caddy:2-alpine
COPY Caddyfile /etc/caddy/Caddyfile
COPY --from=build /app/dist /app/dist
node_modules
dist
.git
:{$PORT:80} {
  root * /app/dist
  encode zstd gzip
  try_files {path} {path}/index.html
  file_server
  handle_errors {
    rewrite * /{err.status_code}.html
    file_server
  }
}

Railway

Railway compila el Dockerfile siempre que lo encuentra, así que no hay nada que configurar. Sin el Dockerfile, añade la variable de servicio RAILPACK_SPA_OUTPUT_DIR=dist y conserva el Caddyfile: Railpack servirá dist/ a través de él en lugar de su configuración por defecto para SPA, que responde a las URL desconocidas con la página de inicio.

Fly.io

Ejecuta fly launch para crear la app y luego fly deploy.

# Fly.io
app = "my-site"

[http_service]
  internal_port = 80
  force_https = true
  auto_stop_machines = "stop"
  auto_start_machines = true
  min_machines_running = 0

Heroku

Heroku compila el Dockerfile a través de heroku.yml. Cambia la app al stack de contenedores una vez con heroku stack:set container y luego ejecuta git push heroku main.

# Heroku
build:
  docker:
    web: Dockerfile

Google Cloud Run

Ejecuta gcloud run deploy --source . desde la raíz del proyecto: Cloud Build compila el Dockerfile y Cloud Run lo sirve.

Tu propio servidor

Copia la compilación al servidor con rsync -a --delete dist/ you@server:/var/www/my-site/ y sírvela con Caddy o nginx: las URL limpias y 404.html funcionan igual que en los servicios de arriba. Caddy obtiene por sí solo un certificado HTTPS para el dominio; con nginx, añade uno con certbot.

example.com {
  root * /var/www/my-site
  encode zstd gzip
  try_files {path} {path}/index.html
  file_server
  handle_errors {
    rewrite * /{err.status_code}.html
    file_server
  }
}
server {
  listen 80;
  server_name example.com;
  root /var/www/my-site;

  location / {
    try_files $uri $uri/index.html =404;
  }

  error_page 404 /404.html;
}

Antes de publicar