Sitios estáticos
Despliega sitios estáticos y SPA en Skiffly — HTML plano con un `Staticfile`, Vite, Astro, SvelteKit, Angular, Create React App, Next.js `output: "export"`, Hugo y Jekyll, el servidor Caddy que usa Railpack, fallbacks para SPA, cabeceras y App Sleeping.
Un sitio estático en Skiffly es un servicio cuyo contenedor ejecuta Caddy sirviendo un directorio. Railpack lo configura en dos situaciones: un repositorio que solo contiene archivos, y un build de JavaScript cuya salida es estática. En ambos casos el servicio recibe un dominio con TLS, puede ponerse en reposo cuando está inactivo y cuesta una fracción de un vCPU.
Archivos planos#
El proveedor staticfile de Railpack se activa cuando el directorio raíz contiene un Staticfile, un index.html, un directorio public/, o cuando RAILPACK_STATIC_FILE_ROOT está definida. El directorio servido es, en este orden: RAILPACK_STATIC_FILE_ROOT, root en Staticfile, public/, y luego ..
root: site
index_fallback: true # SPA: las rutas sin coincidencia sirven index.htmlNo se ejecuta ningún paso de build. Haz commit de un Caddyfile en la raíz para reemplazar el que trae Railpack por defecto (cabeceras personalizadas, redirecciones, encode gzip, autenticación básica). Caddy escucha en PORT; no hay nada que configurar.
Sitios con build (Vite, Astro, Angular, CRA, …)#
Con un package.json, el proveedor de Node ejecuta npm run build y, cuando reconoce un framework estático, sirve la salida con Caddy en lugar de arrancar un proceso de Node:
| Framework | Se detecta por | Salida servida |
|---|---|---|
| Vite (React, Vue, Svelte, Solid) | vite.config.* o vite build en el script de build | dist/ |
| Astro | astro.config.* con output distinto de server | dist/ |
| Next.js | output: "export" en next.config.* | out/ |
| Create React App | react-scripts | build/ |
| Angular | angular.json (RAILPACK_ANGULAR_PROJECT para workspaces con varios proyectos) | dist/<project>/browser |
| React Router / Remix en modo SPA | config o react-router build | build/client |
| Expo web | expo.web.output = static o single | dist/ |
Cambia el directorio con RAILPACK_SPA_OUTPUT_DIR=build; desactiva el modo estático (para ejecutar el servidor propio del framework) con RAILPACK_NO_SPA=true — necesario para SvelteKit con adapter-node, Nuxt, Astro SSR y Next.js sin output: "export". SvelteKit con adapter-static y generate de Nuxt producen un directorio que Railpack no detecta por nombre: apunta RAILPACK_SPA_OUTPUT_DIR a él (build, .output/public).
El fallback de SPA (toda ruta desconocida → index.html) está activo en este modo, así que los routers del lado del cliente funcionan.
Hugo, Jekyll, MkDocs y otros generadores#
Railpack no tiene proveedor para estos. Dos formas que funcionan hoy:
- A través del proveedor de Node. Agrega un
package.jsoncuyo scriptbuildejecute el generador ("build": "hugo --minify"con el paquetehugo-bin, o"build": "npx @11ty/eleventy"), y apuntaRAILPACK_SPA_OUTPUT_DIRal directorio de salida (public,_site). Railpack lo sirve con Caddy. - Dockerfile. Un build de dos etapas funciona para cualquier generador, incluidos los de Python (MkDocs, Pelican, Sphinx):
FROM hugomods/hugo:exts AS build
WORKDIR /src
COPY . .
RUN hugo --minify
FROM caddy:2-alpine
COPY --from=build /src/public /srv
CMD ["sh", "-c", "caddy file-server --root /srv --listen :$PORT"]La plantilla hugo-site del catálogo es la mitad de runtime de esto (Caddy sirviendo un volumen en /srv); un repositorio con el Dockerfile de arriba la reemplaza.
Cabeceras, redirecciones, barras finales#
Pon un Caddyfile en la raíz del repositorio para personalizar el servidor que incluye Railpack (se usa tal cual, así que conserva :{$PORT} como dirección):
:{$PORT} {
root * /app/dist
encode gzip zstd
header /assets/* Cache-Control "public, max-age=31536000, immutable"
header Cache-Control "public, max-age=300"
redir /old-page /new-page permanent
try_files {path} /index.html
file_server
}/app es el directorio de trabajo de la imagen de runtime de Railpack; ajusta root al directorio de salida.
Variables de entorno#
Los builds estáticos incrustan las variables durante el build (VITE_*, PUBLIC_*, NEXT_PUBLIC_*). Defínelas como variables del servicio — todas se pasan al build — y lanza un nuevo build después de cambiarlas (un reinicio no vuelve a construir).
Dominios, reposo, costo#
skiffly domainte da<name>-<id>.skiffly.cloud; los dominios propios necesitan un CNAME y un registro TXT (Redes).- App Sleeping (
sleep: true) va bien para sitios de documentación y landing pages: tras 10 minutos de inactividad el contenedor se detiene y el siguiente visitante espera unos segundos. Los sitios estáticos casi no necesitan memoria: Configuración → Recursos → 0.25 vCPU / 256 MiB. - Skiffly todavía no tiene un CDN delante del nodo; para latencia global, pon Cloudflare (CNAME proxied) delante del dominio propio.
Problemas comunes#
| Síntoma | Solución |
|---|---|
Arranca un proceso de Node en lugar de Caddy (next start, vite preview) | el framework no se reconoció como estático: define RAILPACK_SPA_OUTPUT_DIR, o para Next.js agrega output: "export" |
| Los enlaces profundos devuelven 404 | index_fallback: true en Staticfile, o try_files {path} /index.html en un Caddyfile propio |
Página en blanco, la consola muestra 404 para /assets/* | la app se construyó con el base/publicPath equivocado, o RAILPACK_SPA_OUTPUT_DIR apunta al directorio equivocado |
VITE_API_URL está undefined | define la variable y despliega de nuevo para que el build la incorpore |
El sitio se sirve pero el build corrió para un framework de servidor (RAILPACK_NO_SPA sin definir) | quita RAILPACK_NO_SPA, o define el comando de inicio como el servidor del framework |
| Caddy sirve la raíz del repositorio en lugar del sitio | agrega un Staticfile con root:, o RAILPACK_STATIC_FILE_ROOT |