Article

La vista previa del borrador que le mostraba la página publicada

Nuestra canalización de vista previa validó su token, activó el modo borrador y renderizó un nodo, y luego sirvió la versión publicada. Todas las capas informaron de éxito. El error era una línea que leía una bandera y no hacía nada con ella.
July 15, 2026
Topics:Headless CMSContent ModelingDeveloper Experience
Tags:DrupalNext.jsGraphQL

Un editor abre una página en Drupal, hace clic en Vista previa y recibe la versión que ya está en vivo en el sitio. No es un error. No es una pantalla en blanco. No es un bucle de redirección. Solo el contenido equivocado, renderizado a la perfección.

Esta es la peor forma que puede adoptar un error. No hay nada que buscar con grep. Los registros están limpios, el estado HTTP es 200, y la única persona que puede detectar la falla es un editor que recuerda lo que escribió hace treinta segundos y nota que falta.

Todas las capas dijeron que sí

La vista previa desacoplada en Drupal y Next.js es una cadena, y la nuestra era casi enteramente correcta. El módulo next emitía una URL de vista previa firmada. El iframe cargaba. Nuestra ruta /api/draft validaba la firma del lado del servidor y llamaba a draftMode().enable(). El navegador recibía la cookie de borrador. /preview/<slug> se renderizaba a través de los componentes normales. Hasta el banner de borrador aparecía en la parte superior de la página, anunciando que usted estaba viendo un borrador.

Usted no estaba viendo un borrador.

La línea que leía una bandera y la ignoraba

Esta es la ruta de vista previa tal como estaba, reducida a la parte que importa:

const draft = await draftMode()

const node = await getNodeByPath(slug, locale)
if (!node) {
  notFound()
}

// Outside draft mode, show published content only.
if (!draft.isEnabled && node.status === false) {
  notFound()
}

Léalo con atención. draft.isEnabled se usa, en la penúltima línea. Decide si un nodo no publicado debe devolver 404. Lo que nunca hace es cambiar lo que se obtiene. Hay exactamente un cargador, getNodeByPath, y se ejecuta tanto si está en vista previa como si no.

Por eso el error sobrevivió a la revisión, a TypeScript y a un linter. Una variable sin usar se señala. Una variable usada para el propósito equivocado parece código que funciona. La ruta consultaba el modo borrador para una comprobación de permisos y luego cargaba los mismos datos que carga siempre.

Por qué la consulta subyacente nunca podía funcionar

El cargador pedía el nodo a través del campo route de GraphQL:

query NodeByPath($path: String!) {
  route(path: $path) { ... }
}

route resuelve un alias de ruta a su entidad y devuelve la revisión predeterminada. Ese es el comportamiento correcto y deseable para un sitio público: es lo que hace que la página principal muestre contenido publicado. Pero en Drupal, un borrador de un nodo ya publicado no es la revisión predeterminada. Es una revisión adelantada: más reciente, no predeterminada, deliberadamente no es lo que el tráfico anónimo debe ver.

Así que la consulta funcionaba exactamente como estaba diseñada. Devolvía la revisión predeterminada, que para cualquier nodo publicado es la copia publicada. La maquinaria de vista previa estaba completa salvo por la única capacidad para la que existía: la de obtener un borrador.

El modo de falla difería según el estado del nodo, lo que enturbió el diagnóstico:

  • Nodo publicado con un borrador: la vista previa renderiza la versión publicada. Parece bien. Está mal.
  • Nodo nunca publicado: la revisión predeterminada no está publicada, el rol del cliente público no puede verla, la consulta devuelve null y la ruta devuelve 404.

Un error, dos síntomas, ninguno de ellos un mensaje de error.

La solución: un resolutor que carga una revisión, y el acceso comprobado sobre esa revisión

Añadimos un campo GraphQL, wlDraftPreview(path:, resourceVersion:), respaldado por un productor de datos que hace lo que route deliberadamente no hace:

  • Resolver el alias a una ruta interna /node/N.
  • Cargar la revisión más reciente, prefiriendo la revisión más reciente que afecte a la traducción, para que un borrador en español no se resuelva en silencio a la copia de trabajo en inglés.
  • Respetar un id:<vid> explícito cuando Drupal pide una revisión histórica concreta, que es lo que envía el módulo next cuando un editor previsualiza una revisión antigua en lugar de la copia de trabajo.
  • Comprobar access('view') sobre el objeto de revisión cargado, no sobre el nodo.

Ese último punto es el que soporta la carga. El acceso se evalúa contra lo que usted realmente pretende renderizar. Un borrador no publicado pasa solo si el llamador tiene el permiso view any unpublished content de content_moderation. No hay una rama separada de "¿está permitido?" que mantener sincronizada con la lógica de carga: el objeto que se devuelve es el objeto que se autoriza.

La parte que sigue diseñada para ser silenciosa

El nuevo cargador recurre al nodo publicado cuando el cliente de vista previa no está configurado. Eso es deliberado: un contenedor de compilación sin credenciales de vista previa debe renderizar el sitio, no fallar. Pero significa que la firma de la falla nunca cambió. Si la vista previa vuelve a romperse, volverá a parecer una página que funciona.

Así que la prueba de aceptación tiene que escribirse para detectar un éxito, no un error. La nuestra es una sola frase: abra un borrador de un nodo ya publicado y confirme que ve sus ediciones no publicadas. No "confirme que la vista previa carga". No "confirme que no hay errores". Ambas pasan estando roto. La única pregunta que vale la pena hacer es si los bytes en pantalla son los que aún no están en vivo.

La lección general es barata de enunciar y cara de aprender. Cuando el modo degradado de un sistema es "servir algo razonable", la ausencia de un error no le dice absolutamente nada. Pruebe lo que solo es cierto cuando funciona.