En el capítulo 2 de este nivel construiste el dist/: el bundle de producción comprimido,
con hashes y listo para servirse. En el capítulo 3 montaste el pipeline de CI que verifica
cada push. En ese pipeline dejaste un job deploy: con needs: ci y un echo "publicando...".
Ese hueco es exactamente lo que rellenas aquí.
Publicar en internet es el paso que hace que todo lo anterior importe. Un build que nadie puede visitar no es un producto; es un fichero en una carpeta de tu portátil.
Desplegar una SPA es servir ficheros estáticos#
Cuando ejecutas pnpm build, Vite produce una carpeta dist/ con ficheros estáticos:
index.html, uno o varios .js, uno o varios .css y los assets (imágenes, fuentes).
No hay ningún proceso Node esperando peticiones. No hay lógica de servidor. Son ficheros.
Desplegar esa carpeta significa ponerla en un servidor web que los sirva por HTTP. Cualquier
navegador que pida https://tu-dominio.com/ recibirá el index.html y, a partir de ahí,
el navegador carga el JS y React toma el control.
Eso tiene una consecuencia importante: una SPA se puede hospedar de forma gratuita en muchas plataformas porque no necesitan ejecutar código por ti. Solo almacenan ficheros y los sirven. GitHub Pages, Vercel, Netlify y decenas de alternativas ofrecen esto sin coste para proyectos personales o de aprendizaje.
La diferencia con lo que verás en el Nivel 10 (backend con Express) es exactamente esta: un servidor de aplicación sí ejecuta código por cada petición. Una SPA, no.
GitHub Pages con Actions#
GitHub Pages es el hosting estático de GitHub. Está integrado en el repositorio: no hace falta crear ninguna cuenta nueva ni configurar ningún servicio externo. Para activarlo basta con ir a Settings → Pages en tu repositorio y elegir “GitHub Actions” como fuente.
El flujo de publicación tiene cuatro pasos que siempre van en este orden:
# Paso 1: prepara el entorno de Pages en el runner
- name: Configurar GitHub Pages
uses: actions/configure-pages@v5
# Paso 2: empaqueta el contenido de dist/ como artifact de Pages
# path: dist/ indica la carpeta que Vite genera al construir
- name: Subir artifact de Pages
uses: actions/upload-pages-artifact@v4
with:
path: dist/
# Paso 3: despliega el artifact en GitHub Pages y devuelve la URL final
- name: Desplegar en GitHub Pages
uses: actions/deploy-pages@v4Para que el workflow pueda publicar, necesita tres permisos que van al nivel del workflow, fuera de los jobs:
# Estos permisos son obligatorios; sin alguno de los tres, el deploy falla con 403
permissions:
# Leer el código del repositorio
contents: read
# Subir el artifact al almacenamiento de Pages
pages: write
# Obtener el token de autenticación de Pages (OIDC)
id-token: writeY el job de deploy debe declarar que trabaja en el entorno de GitHub Pages:
jobs:
deploy:
runs-on: ubuntu-latest
# environment registra este deploy en la pestaña Environments del repositorio
environment: github-pages
steps:
# ... los cuatro pasos de arribaLa trampa del base#
Cuando publicas en Pages, tu app vive en un subdirectorio:
https://tu-usuario.github.io/team-builder/No en la raíz del dominio. Vite, sin configuración adicional, genera rutas absolutas para los assets:
/assets/index-a1b2c3d4.js
/assets/styles-e5f6g7h8.cssEl navegador resuelve esas rutas contra la raíz del dominio y pide:
https://tu-usuario.github.io/assets/index-a1b2c3d4.jsEse fichero no existe en esa ruta. El resultado es silencioso: la página carga, aparece en blanco y en la consola de red hay 404. Es el fallo número uno al desplegar en Pages, y no hay ningún mensaje de error que lo explique.
La solución es configurar base en vite.config.ts antes de hacer el primer deploy:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
// base debe coincidir exactamente con el nombre de tu repositorio en GitHub
base: '/team-builder/',
plugins: [react()],
});Con base configurado, Vite genera rutas relativas al subdirectorio:
/team-builder/assets/index-a1b2c3d4.jsy el navegador las encuentra. Haz este cambio en tu proyecto antes de copiar el workflow: si no, el workflow desplegará una app rota.
Vercel: cero configuración#
Vercel es una plataforma de hosting que detecta el tipo de proyecto automáticamente. Para desplegar el Team Builder en Vercel no necesitas escribir ningún workflow: conectas el repositorio desde el panel web de Vercel y cada push a main desencadena el build y el deploy sin más configuración.
La diferencia clave con Pages es que Vercel sirve desde la raíz de un dominio propio:
https://team-builder.vercel.app/No hay subdirectorio, así que el problema del base no existe. Tampoco hay que activar
permisos ni escribir pasos de YAML.
El trade-off es claro. Pages te da control total: el workflow es tuyo, está versionado en el repositorio, el equipo puede auditarlo y modificarlo. Cualquier ingeniería puede leer qué pasa exactamente con cada push. Vercel te da comodidad: conectas el repo y funciona, pero confías en que la plataforma hace lo correcto por ti. Ambas opciones son válidas y están en uso en proyectos profesionales reales.
Preview deploys: una URL por cada PR#
En el capítulo 1 de este nivel abriste Pull Requests con gh pr create. Un PR representa
un cambio pendiente de revisión. Revisar un PR mirando solo el diff del código tiene un
límite: no ves cómo se comporta la UI en el navegador.
Un preview deploy resuelve esto: cuando alguien abre un PR, la plataforma construye la app con ese código y la publica en una URL temporal con el número del PR:
https://team-builder-pr-42.vercel.app/El revisor abre esa URL, usa la app real, navega, interactúa, y detecta problemas que no se ven en el código. Cuando el PR se fusiona a main, el preview se destruye. Si hay varios PRs abiertos, cada uno tiene su propia URL.
En Vercel esto funciona sin configuración adicional: cada PR genera su preview automáticamente. En GitHub Pages no hay soporte nativo para preview deploys: conseguirlo requiere scripts adicionales que publican en subdirectorios temporales o en repositorios separados. Es posible pero complejo. Si los preview deploys son importantes en tu flujo de trabajo, Vercel es la opción más directa.
Variables de entorno y secrets en producción#
En el Nivel 4 aprendiste la regla: una variable que empieza por VITE_ se incrusta en el
bundle de producción. Es texto plano dentro de un fichero .js que el navegador descarga.
Cualquiera que abra DevTools puede leerla.
Esa regla no cambia en producción. La aplicarla bien aquí tiene consecuencias económicas y de seguridad reales.
Lo que sí puede ir en una variable VITE_: configuración no sensible que el cliente
necesita conocer. La URL de una API pública, el nombre de tu proyecto para analytics,
una clave de un servicio de mapas sin privilegios de escritura.
# En el paso de build del workflow, la variable se inyecta desde el secret del repositorio
- name: Construir la app
run: pnpm build
env:
# VITE_API_URL acabará en el bundle: asegúrate de que no es un dato sensible
VITE_API_URL: ${{ secrets.VITE_API_URL }}Lo que nunca puede ir en una variable VITE_: claves privadas de API con privilegios
de escritura, tokens de acceso, contraseñas de bases de datos, secretos de firma de JWT.
En un deploy estático no hay servidor donde esconderlos: acabarían en el bundle, en el
navegador del usuario y, por tanto, en manos de cualquiera que abra las DevTools.
Si el Team Builder necesita operar con datos sensibles, la arquitectura correcta es:
navegador (React) → tu backend (Express, Nivel 10) → API externaEl cliente llama a un endpoint propio de tu backend. El backend guarda el secret en sus variables de entorno de servidor (que nunca llegan al cliente) y llama a la API externa. El cliente nunca ve la clave.
El secret del repositorio (Settings → Secrets and variables → Actions) es donde guardas
los valores que el workflow necesita en tiempo de ejecución. GitHub los cifra, los enmascara
en los logs y nunca los expone. Pero recuerda: si los inyectas como VITE_, siguen acabando
en el bundle.
Conectar el deploy al CI/CD#
El capítulo 3 dejó este esqueleto en el workflow:
jobs:
ci:
runs-on: ubuntu-latest
steps:
- run: pnpm test
- run: pnpm build
deploy:
# needs: ci hace que deploy espere a que ci pase en verde
needs: ci
# Solo se despliega en pushes a main, nunca en PRs ni en otras ramas
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- run: echo "publicando..."Ahora rellenas ese echo "publicando..." con los pasos reales de GitHub Pages. El resultado
es un pipeline completo:
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v6
with:
node-version: 24
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm test
- run: pnpm build
deploy:
needs: ci
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: github-pages
steps:
# Cada job parte de una máquina nueva y limpia: el checkout es necesario aquí también
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v6
with:
node-version: 24
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
# Se construye de nuevo porque el dist/ del job ci no persiste entre jobs
- run: pnpm build
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v4
with:
path: dist/
- uses: actions/deploy-pages@v4El status check que bloquea el merge de los PRs (que configuraste con Branch protection rules
en el capítulo 1) es el job ci. El job deploy solo corre cuando ese check pasa y el push
llega a main. El código roto nunca llega a producción.
El CDN y tu dominio#
Cuando Pages o Vercel sirven tu app, no lo hacen desde un único servidor. Lo hacen desde una red de distribución de contenido (CDN): decenas o cientos de servidores repartidos por todo el mundo que tienen una copia de tus ficheros. Cuando alguien en Tokio abre tu Team Builder, recibe los ficheros desde el servidor más cercano a Tokio, no desde un centro de datos en Estados Unidos. La latencia se reduce de cientos de milisegundos a decenas.
Los assets con hash que genera Vite (como index-a1b2c3d4.js) son perfectos para el CDN.
El CDN puede configurarlos con una caducidad de caché muy larga: si el nombre no cambia, el
contenido no cambia, y el servidor puede servirlo sin consultar el origen. Cuando publicas
una nueva versión, el hash cambia, el CDN ve un nombre nuevo, lo descarga del origen y lo
sirve fresco a todos los usuarios.
Sin hashes, el CDN serviría un fichero llamado index.js sin saber si es el del deploy
de hoy o el de hace seis meses. Con hashes, tiene certeza. Por eso el capítulo 2 explicaba
que los hashes son una decisión de arquitectura, no un detalle cosmético.
Si quieres conectar un dominio propio (teambuilder.tudominio.com) a Pages o a Vercel,
basta con ir al panel de la plataforma, añadir el dominio y apuntar el registro DNS donde
compres el dominio al servidor que te indique la plataforma. Los DNS los viste en el Nivel 0:
el registro CNAME o A apunta tu dominio al host. La plataforma se encarga del resto, incluido
el certificado HTTPS.
Cuando un deploy a main rompe producción, la forma más rápida de volver a un estado bueno
es revertir el commit problemático con git revert —el comando seguro que viste en el
capítulo 1: crea un commit nuevo que deshace los cambios de otro, sin reescribir la historia,
así que vale en una rama compartida como main— y dejar que el pipeline redepliegue la versión
anterior de forma automática. GitHub Pages y Vercel también
conservan el historial de deploys en su panel, donde puedes restaurar manualmente cualquier
deploy anterior sin tocar el código.
Comprueba lo que sabes#
Pregunta 1 de 7
¿Qué significa desplegar una SPA?
Tu turno#
El ejercicio se hace en local, sobre el Team Builder de React que vienes construyendo desde
el Nivel 7. Necesitas el repositorio subido a GitHub y que pnpm lint, pnpm test y
pnpm build pasen en verde en tu máquina antes de empezar.
Ejercicio · hazlo en local
Deploy del Team Builder en GitHub Pages
Sobre el repositorio de tu Team Builder de React (el que vienes construyendo desde el Nivel 7), crea el fichero `.github/workflows/deploy.yml` y verifica que la app se publica en GitHub Pages tras un push a main. Las soluciones muestran el workflow completo de cada tier con comentarios que explican cada decisión. Haz el tuyo primero y luego compara.
Paso 1: Deploy mínimo funcional
- Añades `base: '/nombre-del-repo/'` en `vite.config.ts` (con el nombre exacto de tu repositorio) antes de crear el workflow.
- Activas GitHub Pages en modo "GitHub Actions" en Settings > Pages del repositorio.
- Creas `.github/workflows/deploy.yml` con los permisos correctos (`contents: read`, `pages: write`, `id-token: write`), el entorno `github-pages` y los pasos: checkout → instalar pnpm → configurar Node → instalar dependencias → build → configure-pages → upload-pages-artifact → deploy-pages.
- Haces commit y push a main; en la pestaña Actions ves el workflow ejecutarse en verde.
- Entras en la URL publicada (`https://tu-usuario.github.io/nombre-del-repo/`) y el Team Builder aparece y funciona.
Paso 2: CI antes del deploy con concurrencia
- Separas en dos jobs: `ci` (lint, tests, build) y `deploy` (publicar en Pages).
- El job `deploy` usa `needs: ci`: solo se ejecuta si el CI pasa en verde.
- Añades `concurrency` con `group: pages` y `cancel-in-progress: false` para que dos pushes seguidos no pisen el deploy en curso.
- Puedes explicar por qué el job `deploy` vuelve a hacer checkout e instalar dependencias aunque el job `ci` ya lo hizo (cada job parte de una máquina distinta y limpia).
- En la pestaña Actions ves los dos jobs encadenados: ci en verde → deploy en verde → URL publicada.
Paso 3: URL visible, artifact reutilizado y gestión de entornos
- El job `ci` sube el `dist/` como artifact con `actions/upload-pages-artifact`; el job `deploy` lo descarga directamente con `actions/deploy-pages` sin volver a construir la app.
- El entorno del job `deploy` incluye `url: ${{ steps.deployment.outputs.page_url }}`; al terminar, GitHub muestra un enlace directo a la app en el resumen del workflow.
- Incluyes un bloque de comentarios en el YAML que explica la regla de secrets en deploys estáticos: qué puede ir en una variable `VITE_`, qué nunca puede ir ahí y cómo pasarla via `env: VITE_API_URL: ${{ secrets.VITE_API_URL }}`.
- Puedes explicar en qué se diferencia este tier del "mejor" en términos de eficiencia (se construye una vez, se despliega lo verificado) y de visibilidad (URL accesible desde la interfaz de Actions).
- La app publicada funciona correctamente y la URL aparece en la interfaz de Actions al terminar el workflow.
Cómo hacerlo en local
Clona el repositorio del curso, entra en la carpeta del ejercicio y abre el
index.html en tu navegador. Toda tu solución va en
solucion.js.
git clone <repo>
cd exercises/nivel-9/deploy
# abre index.html en el navegador y edita solucion.js Ver soluciones
# Tier ok — deploy mínimo a GitHub Pages
# Un solo job construye la app con pnpm y la publica en GitHub Pages.
# La app queda online tras cada push a main.
# Límite de este tier: no corre tests ni lint antes de desplegar.
# Puedes publicar código roto sin que nadie lo impida.
name: Deploy
on:
push:
branches:
# Solo se dispara cuando el push va a main, no en ramas de trabajo
- main
# Permisos que el workflow necesita para publicar en GitHub Pages.
# Sin estos tres permisos, actions/deploy-pages falla con 403.
# contents: read — leer el código del repositorio
# pages: write — subir el artifact al almacenamiento de Pages
# id-token: write — obtener el token de autenticación de Pages (OIDC)
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
# environment le dice a GitHub que este job publica en el entorno "github-pages".
# GitHub registra el deploy en la pestaña Environments del repositorio.
environment: github-pages
steps:
# Paso 1: descarga el código del repositorio en la máquina virtual
- name: Obtener el código
uses: actions/checkout@v6
# Paso 2: instala pnpm antes de usarlo
- name: Instalar pnpm
uses: pnpm/action-setup@v4
with:
# Versión de pnpm; debe coincidir con la de tu proyecto
version: 9
# Paso 3: instala Node.js
- name: Configurar Node.js
uses: actions/setup-node@v6
with:
node-version: 24
# Activa la caché de dependencias de pnpm para acelerar las siguientes ejecuciones
cache: 'pnpm'
# Paso 4: instala las dependencias exactas del lockfile
- name: Instalar dependencias
run: pnpm install --frozen-lockfile
# Paso 5: construye la app para producción
# IMPORTANTE: antes de llegar aquí, asegúrate de que vite.config.ts tiene
# base: '/team-builder/' (con el nombre exacto de tu repositorio).
# Sin esa configuración, Vite genera rutas absolutas (/assets/...) que el
# navegador resuelve contra la raíz del dominio en vez de contra el subdirectorio,
# y la app se carga en blanco con 404 en la consola de red.
- name: Construir la app
run: pnpm build
# Paso 6: prepara el entorno de Pages en la máquina virtual
# Configura el runner para que pueda interactuar con GitHub Pages
- name: Configurar GitHub Pages
uses: actions/configure-pages@v5
# Paso 7: empaqueta el contenido de dist/ como artifact de Pages
# path: dist/ indica la carpeta que Vite genera al construir
- name: Subir artifact de Pages
uses: actions/upload-pages-artifact@v4
with:
path: dist/
# Paso 8: despliega el artifact en GitHub Pages
# Este paso publica el contenido y devuelve la URL final en page_url
- name: Desplegar en GitHub Pages
uses: actions/deploy-pages@v4 Por qué este nivel
- El deploy mínimo que cualquier proyecto debería tener: cada push a main construye la app y la publica. Con los permisos correctos, el entorno declarado y los cuatro pasos de Pages (configure → upload → deploy), la app está en internet sin intervención manual.
- Su límite es que despliega sin pasar tests ni lint: una regresión o un build roto llegan directamente a producción sin que nadie lo impida. El tier "mejor" cierra ese hueco separando el CI del deploy.
# Tier mejor — CI antes del deploy
# Supera al tier ok en dos aspectos:
# 1. Separa en dos jobs: "ci" corre lint, tests y build; "deploy" publica.
# El deploy solo se ejecuta si el job ci termina en verde.
# Desplegar sin pasar el CI antes es publicar a ciegas: cualquier test roto,
# error de ESLint o fallo de build llega directamente a los usuarios.
# 2. Añade concurrencia: si haces dos pushes seguidos a main, el workflow del
# primero se cancela en cuanto llega el segundo, evitando que dos deploys
# compitan por publicar en Pages al mismo tiempo.
# cancel-in-progress: false es el patrón oficial de Pages: no canceles un
# deploy que ya está en curso porque dejaría Pages en un estado indeterminado;
# solo cancela los runs que todavía no han llegado al job de deploy.
# Límite de este tier: el job de deploy no expone la URL publicada en la interfaz
# de Actions, así que hay que ir a Settings > Pages para encontrarla.
name: Deploy
on:
push:
branches:
- main
permissions:
contents: read
pages: write
id-token: write
# cancel-in-progress: false es deliberado en workflows de Pages.
# Si canceláramos un deploy a medias, Pages podría quedar en un estado intermedio.
# Lo que sí queremos cancelar son los runs que están esperando en cola antes de
# que lleguen al job de deploy; eso lo consigue cancel-in-progress: false combinado
# con el group de concurrencia: el segundo push cancela el primer run si todavía
# está en el job ci, pero respeta el deploy si ya empezó.
concurrency:
group: pages
cancel-in-progress: false
jobs:
# Job 1: comprueba que el código está en buen estado antes de publicar
ci:
runs-on: ubuntu-latest
steps:
- name: Obtener el código
uses: actions/checkout@v6
- name: Instalar pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Configurar Node.js con caché de pnpm
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'pnpm'
- name: Instalar dependencias
run: pnpm install --frozen-lockfile
# El linter caza problemas de estilo y errores comunes antes de publicar
- name: Linting
run: pnpm lint
# Los tests comprueban que la lógica funciona correctamente
- name: Ejecutar tests
run: pnpm test
# El build verifica que el bundle de producción se genera sin errores.
# IMPORTANTE: vite.config.ts debe tener base: '/team-builder/' (con el nombre
# exacto de tu repositorio). Sin esa línea, los assets usan rutas absolutas
# que dan 404 en Pages y la app se carga en blanco.
- name: Construir la app
run: pnpm build
# Job 2: publica en GitHub Pages, pero solo si el job ci pasó en verde
deploy:
# needs: ci hace que este job espere a que ci termine con éxito.
# Si ci falla, deploy no se ejecuta: el código roto no llega a producción.
needs: ci
runs-on: ubuntu-latest
environment: github-pages
steps:
# El checkout es necesario aquí también: cada job parte de una máquina limpia
- name: Obtener el código
uses: actions/checkout@v6
- name: Instalar pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Configurar Node.js con caché de pnpm
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'pnpm'
- name: Instalar dependencias
run: pnpm install --frozen-lockfile
# Construimos de nuevo en el job de deploy porque cada job es una máquina
# distinta y el dist/ del job ci no persiste entre jobs por defecto.
# (En el tier excelente, el ci sube el dist/ como artifact y el deploy lo descarga,
# evitando construir dos veces. Aquí preferimos la claridad sobre la eficiencia.)
- name: Construir la app
run: pnpm build
- name: Configurar GitHub Pages
uses: actions/configure-pages@v5
- name: Subir artifact de Pages
uses: actions/upload-pages-artifact@v4
with:
path: dist/
- name: Desplegar en GitHub Pages
uses: actions/deploy-pages@v4 Por qué es mejor que el anterior
- Dos jobs encadenados con `needs: ci`: lint, tests y build tienen que pasar en verde antes de que el deploy empiece. Si el CI falla, la producción no cambia. La concurrencia evita que dos deploys compitan al mismo tiempo.
- Su límite es que construye la app dos veces (una en ci, otra en deploy) y la URL publicada no aparece en la interfaz de Actions. El tier "excelente" resuelve ambas cosas.
# Tier excelente — URL visible, artifact reutilizado y gestión de entornos
# Supera al tier mejor en tres aspectos:
# 1. La URL publicada aparece en la interfaz de GitHub Actions al terminar el deploy,
# sin tener que ir a Settings > Pages para encontrarla.
# 2. El job ci sube el dist/ como artifact; el job deploy lo descarga en vez de
# construir la app por segunda vez, ahorrando tiempo y garantizando que lo que
# se despliega es exactamente lo que el CI verificó.
# 3. Añade un bloque de comentarios sobre variables de entorno y secrets en deploys
# estáticos: qué es público, qué nunca puede ir al bundle y cómo proteger datos
# sensibles.
#
# Nota sobre preview deploys por PR:
# En Vercel, cada PR recibe automáticamente su propio subdominio de preview
# (https://tu-proyecto-pr-42.vercel.app/) sin configuración adicional.
# En GitHub Pages no hay soporte nativo de preview por PR: conseguirlo requiere
# scripts adicionales que publican en subdirectorios temporales o en repos de preview
# separados. Es posible pero complejo; si los preview deploys son importantes para tu
# flujo, Vercel es la opción más directa.
name: Deploy
on:
push:
branches:
- main
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
# Job 1: lint, tests, build y subida del artifact
ci:
runs-on: ubuntu-latest
steps:
- name: Obtener el código
uses: actions/checkout@v6
- name: Instalar pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Configurar Node.js con caché de pnpm
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'pnpm'
- name: Instalar dependencias
run: pnpm install --frozen-lockfile
- name: Linting
run: pnpm lint
- name: Ejecutar tests
run: pnpm test
# IMPORTANTE: vite.config.ts debe tener base: '/team-builder/' (con el nombre
# exacto de tu repositorio). Sin esa línea, Vite genera rutas absolutas
# (/assets/index-xxxx.js) que el navegador resuelve contra la raíz del dominio,
# no contra el subdirectorio de Pages, y la app se carga en blanco con 404.
- name: Construir la app
run: pnpm build
# -----------------------------------------------------------------------
# VARIABLES DE ENTORNO Y SECRETS EN UN DEPLOY ESTÁTICO
# -----------------------------------------------------------------------
# Las variables VITE_ se incrustan en el bundle durante el build.
# Son cadenas de texto dentro de un fichero .js que cualquiera puede leer:
# la URL de una API pública, el nombre de tu proyecto, una clave de analytics
# sin privilegios. Son adecuadas para configuración no sensible.
#
# NUNCA pongas en una variable VITE_ un secret de verdad:
# una clave privada de API, un token de acceso, una contraseña de base de datos,
# un secreto de firma de JWT. En un deploy estático no hay servidor donde
# esconderlo: el secret termina en el bundle, en el navegador del usuario y,
# por tanto, en manos de cualquiera que abra las DevTools.
#
# Si tu app necesita operar con datos sensibles, la arquitectura correcta es:
# el cliente llama a un endpoint de tu propio backend, que sí tiene entorno
# de servidor seguro (variables de entorno reales, no incrustadas en el bundle),
# y ese backend es el único que usa el secret para llamar a la API externa.
# El cliente nunca ve la clave.
#
# Para pasar una variable VITE_ en el workflow de Actions, declárala como
# secret en Settings > Secrets and variables > Actions y úsala así en el paso
# de build:
# env:
# VITE_API_URL: ${{ secrets.VITE_API_URL }}
# -----------------------------------------------------------------------
# Sube el dist/ como artifact para que el job de deploy lo reutilice.
# Así el CI y el deploy trabajan con el mismo bundle: lo que se prueba
# es exactamente lo que se publica.
- name: Subir dist/ como artifact de Pages
uses: actions/upload-pages-artifact@v4
with:
path: dist/
# Job 2: publica el artifact generado por ci en GitHub Pages
deploy:
needs: ci
runs-on: ubuntu-latest
# environment con url expone la dirección publicada en la interfaz de Actions.
# Al terminar el job, GitHub muestra un enlace directo a la app desplegada
# en el resumen del workflow, sin necesidad de ir a Settings > Pages.
environment:
name: github-pages
# steps.deployment.outputs.page_url lo devuelve actions/deploy-pages
# con la URL final donde la app está publicada
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Configurar GitHub Pages
uses: actions/configure-pages@v5
# actions/deploy-pages descarga automáticamente el artifact subido por
# actions/upload-pages-artifact en el mismo workflow. No hace falta un paso
# de checkout ni de build: el dist/ ya está listo y verificado.
- name: Desplegar en GitHub Pages
# id: deployment permite referenciar los outputs de este paso más arriba,
# en environment.url, para que GitHub muestre el enlace al terminar
id: deployment
uses: actions/deploy-pages@v4 Por qué es mejor que el anterior
- Lo que se despliega es exactamente lo que el CI verificó: el job ci construye una vez y sube el dist/ como artifact; el job deploy lo descarga y lo publica sin reconstruir. Al terminar, la URL aparece en la interfaz de Actions sin tener que ir a Settings > Pages.
- El bloque de comentarios sobre secrets en el YAML no es decorativo: es la documentación que el equipo lee cuando alguien pregunta "¿puedo meter aquí mi clave de API?". La respuesta corta está en el workflow; la larga, en el capítulo.