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.
Dockerfile:ARG NEXT_PUBLIC_PAYLOAD_URLy suENV(ya hecho)..github/workflows/imagen.yml: la línea enbuild-args(ya hecho).- 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.