Saltar a contenido

Payload · Vista previa de borradores

Cómo funciona la vista previa que permite a un editor ver en el storefront real cómo queda una página mientras la edita, sin publicarla. Documento hermano del lado del CMS: cms-payload/docs/integraciones/live-preview-storefront.md.

La decisión de usar la variante del lado del servidor está en ADR-0008.

Cómo funciona

Payload no renderiza nada de la vista previa. Solo decide una URL, la mete en un marco y avisa cuando guardó. Todo lo demás lo hace el storefront.

Admin de Payload                        Storefront
────────────────                        ──────────
livePreview.url (función)
  └─ arma la URL  ─────────────────▶  /api/draft?secret=…&path=…
                                        │ valida el secreto
                                        │ draftMode().enable()  → cookie __prerender_bypass
                                        ▼
                                      /py/mi-pagina
                                        │ lee la cookie
                                        │ pide a Payload draft=true, sin caché, con JWT
                                        ▼
                                      página armada con el borrador
                                        │
      ◀──────── "estoy listo" ─────────┘  (RefreshRouteOnSave)

  editor escribe
  autoguardado (800 ms)
  ──── "payload-document-event" ────▶  router.refresh()
                                        └─ vuelve a pedir la página al servidor

El mensaje que manda Payload no lleva datos: es solo un timbre. El contenido viaja por el camino normal del servidor, el mismo que usa un visitante cualquiera.

Piezas

Archivo Qué hace
cms-payload/src/utilities/generatePreviewPath.ts Arma la URL absoluta al storefront
src/app/api/draft/route.ts Valida el secreto y activa el modo borrador
src/app/api/draft/disable/route.ts Sale del modo borrador
src/lib/payload.ts lecturaDeBorrador(): sin caché y con el JWT del usuario de servicio
src/modules/common/components/RefreshRouteOnSave.tsx Escucha los avisos y refresca
src/proxy.ts frame-ancestors permite embeber solo desde el origen del CMS

Las dos rutas que renderizan contenido de Payload leen draftMode() y montan el componente de refresco solo cuando está activo: [countryCode]/(main)/page.tsx y [countryCode]/(main)/[slug]/page.tsx (esta última también en generateMetadata).

Variables de entorno

La distinción entre build y ejecución importa: una variable NEXT_PUBLIC_* se inlinea en el bundle durante el build, así que ponerla solo en el .env del host no tiene efecto.

Variable Dónde Sin ella
NEXT_PUBLIC_PAYLOAD_URL navegador, build La vista previa no se refresca sola y frame-ancestors queda en 'none'
PREVIEW_SECRET server, ejecución /api/draft responde 501; debe ser idéntica a la del CMS
PAYLOAD_USER_EMAIL, PAYLOAD_USER_PASSWORD server, ejecución Se ve la versión publicada en lugar del borrador, sin error

Una contraseña con # va entre comillas

dotenv —lo que usan Next y Payload— interpreta # como inicio de comentario y trunca el valor. source de bash no lo hace, así que la prueba manual pasa y la aplicación falla con 401. Escribir PAYLOAD_USER_PASSWORD="valor#con#almohadilla".

check-env-variables.js avisa en el log cuando falta cualquiera de las cuatro. Son recomendadas, no obligatorias: si fueran obligatorias, el próximo despliegue fallaría hasta que alguien actualice el secret, bloqueando entregas ajenas a esta funcionalidad.

Qué hace falta para desplegar

NEXT_PUBLIC_PAYLOAD_URL se inlinea en el build y el workflow lleva una lista explícita de variables públicas, así que hay que tocarla en tres lugares. Si falta uno, el build termina bien y la imagen sale con la vista previa a medias.

  1. Dockerfile: ARG NEXT_PUBLIC_PAYLOAD_URL y su ENV (ya hecho).
  2. .github/workflows/imagen.yml: la línea en build-args (ya hecho).
  3. El archivo de entorno en Secret Manager: agregar la variable. Este paso no vive en el repositorio y es el que se olvida.

Las de ejecución (PREVIEW_SECRET, PAYLOAD_USER_*) las escribe el job de despliegue en el .env del host y no entran a la imagen.

Del lado del CMS hace falta STOREFRONT_PREVIEW_URL, que se lee en ejecución y no necesita nada del build.

Probar en local

No usar la IP pelada para los dos servicios

Las cookies se guardan por host e ignoran el puerto. Con el CMS en 192.168.0.220:3000 y el storefront en 192.168.0.220:8001, los dos comparten el mismo frasco de cookies. Como ambos son aplicaciones Next, cada uno tiene su propio identificador de modo borrador, y el CMS borra la cookie que puso el storefront porque no la reconoce. Basta una sola petición al admin para que la vista previa vuelva a mostrar la versión publicada.

Hay que darles nombres distintos que compartan dominio padre. En el /etc/hosts de la máquina donde corre el navegador:

192.168.0.220 cms.compulandia.test shop.compulandia.test

Y después:

Archivo Variable Valor
cms-payload/.env NEXT_PUBLIC_SERVER_URL http://cms.compulandia.test:3000
cms-payload/.env STOREFRONT_PREVIEW_URL http://shop.compulandia.test:8001
.env.local NEXT_PUBLIC_PAYLOAD_URL http://cms.compulandia.test:3000
.env.local PAYLOAD_PUBLIC_URL http://cms.compulandia.test:3000
.env.local NEXT_DEV_ORIGINS agregarle los dos nombres

Los dos servicios tienen que ir por el mismo protocolo: un admin en https no puede embeber un storefront en http, el navegador lo bloquea por contenido mixto.

En los entornos desplegados nada de esto aplica: dev-cms y dev-shop son hosts distintos, así que cada uno tiene sus cookies, y al ser subdominios del mismo dominio registrable la cookie igual viaja dentro del marco.

Cuando algo no anda

Cuatro llaves controlan la cadena y tres fallan en silencio. Conviene recorrerlas en orden en lugar de buscar en el código.

Síntoma Causa probable Dónde mirar
/api/draft responde 401 El secreto no coincide entre los dos proyectos PREVIEW_SECRET en ambos .env
/api/draft responde 501 No hay secreto configurado PREVIEW_SECRET en el storefront
Se ve la versión publicada, sin error El login de servicio falla Buscar [payload] Error en login en el log del servidor
Abre bien pero no se refresca al guardar El origen no coincide exactamente NEXT_PUBLIC_PAYLOAD_URL contra la barra de direcciones del admin
Vuelve a la versión publicada al editar Colisión de cookies entre CMS y storefront Ver "Probar en local"
El panel queda en blanco frame-ancestors no permite el origen del CMS Cabecera Content-Security-Policy de la respuesta

El origen se compara con igualdad literal (event.origin === serverURL), así que http://localhost:3000 y http://192.168.0.220:3000 no son intercambiables aunque sean la misma máquina, y una barra al final rompe la comparación.

Autoguardado

Pages y Posts guardan borradores 800 ms después de que el editor deja de escribir. El template de Payload traía 100 ms, pensado para la variante del lado del cliente, que refresca en el navegador sin consultar al servidor. Con la nuestra, cada guardado dispara un renderizado completo de alrededor de un segundo: a 100 ms los refrescos se encimaban y el panel parecía no actualizarse.

Si se cambia ese número, tener presente que el piso útil es el tiempo que tarda la página en renderizarse.

Referencias