05 / Cómo funciona Lupus

Del contrato al HTML

La sencillez consiste en tener un protocolo predecible para múltiples orígenes, no en eliminar la implementación del frontend.

Todos los recorridos ↗

1. Resolver una URL

La ruta catch-all de Next solicita la misma ruta bajo /ce-api, con prefijo de idioma y query. Drupal decide qué contenido corresponde y aplica acceso. Los errores HTTP no se convierten en éxito: el frontal conserva 403/404 y muestra sus pantallas.

2. Validar la respuesta

parsePage comprueba título, content_format, árbol, metatags y, cuando existe, bloques por región. El contrato inválido produce error explícito. No intentamos ejecutar nombres recibidos como código ni importar componentes arbitrarios.

3. Resolver componentes y slots

El registro React reconoce NttHero, NttSection y los demás componentes editoriales. Casos explícitos representan fichas, teasers, Views y el nuevo ejemplo de módulo/bloque. Los slots anidados se recorren recursivamente. Un elemento no soportado genera un error, no contenido silenciosamente vacío.

4. Generar HTML y añadir interacción

Next genera HTML en servidor. La paginación utiliza el pager recibido; el inspector y el selector usan interacción frontend. Solo renderizamos HTML editorial filtrado del Drupal de confianza; las props de texto se escapan con React. Los assets y la lógica de widgets no viajan dentro del JSON.

Ejemplo real de esta instalación

El mismo renderer presenta el contenido del controlador y el bloque de región. Abre el ejemplo completo y compáralo con su JSON o alterna los cuatro orígenes en el inspector.

Inspeccionar contrato recibido en servidor
{
  "title": "Drupal module / CE",
  "content_format": "json",
  "content": {
    "element": "ntt-module-example",
    "props": {
      "title": "Un módulo Drupal, renderizado en Next.js",
      "description": "Esta ruta devuelve un CustomElement desde un controlador PHP. No es una página Canvas ni un nodo. Drupal resuelve el acceso y añade el bloque configurado; React presenta ambos.",
      "routeName": "ntt_lupus_examples.module"
    }
  },
  "metatags": {
    "meta": [],
    "link": []
  },
  "blocks": {
    "sidebar_first": {
      "canvas_lupus_theme_page_title": {
        "element": "drupal-markup",
        "slots": {
          "default": "\n\n"
        }
      },
      "canvas_lupus_theme_powered": {
        "element": "drupal-markup",
        "slots": {
          "default": "<span>Powered by <a href=\"https://www.drupal.org\">Drupal</a></span>"
        }
      }
    },
    "content": {
      "ntt_delivery_note": {
        "element": "ntt-delivery-note",
        "props": {
          "title": "Un bloque Drupal reutilizable",
          "description": "Este mensaje procede de un bloque configurado en Drupal, no de Canvas ni de un texto fijo en React."
        }
      }
    },
    "header": {
      "canvas_lupus_theme_site_branding": {
        "element": "drupal-markup",
        "slots": {
          "default": "<img src=\"https://dev-headless-lupus.pantheonsite.io/themes/custom/canvas_lupus_theme/logo.svg\" alt=\"Home\" />\nCanvas Lupus POC"
        }
      }
    },
    "primary_menu": {
      "canvas_lupus_theme_main_menu": {
        "element": "drupal-markup",
        "slots": {
          "default": "\n              <ul>\n              <li>\n        <a href=\"/\" data-drupal-link-system-path=\"&lt;front&gt;\">Home</a>\n              </li>\n        </ul>\n  \n\n"
        }
      }
    },
    "secondary_menu": {
      "canvas_lupus_theme_account_menu": {
        "element": "drupal-markup",
        "slots": {
          "default": "\n              <ul>\n              <li>\n        <a href=\"/user/login\" data-drupal-link-system-path=\"user/login\">Log in</a>\n              </li>\n        </ul>\n  \n\n"
        }
      }
    },
    "highlighted": {
      "canvas_lupus_theme_messages": {
        "element": "drupal-markup",
        "slots": {
          "default": "<div data-drupal-messages-fallback class=\"hidden\"></div>"
        }
      }
    },
    "breadcrumb": {
      "canvas_lupus_theme_breadcrumbs": {
        "element": "drupal-markup",
        "slots": {
          "default": "  <nav aria-labelledby=\"system-breadcrumb\">\n    <h2 id=\"system-breadcrumb\" class=\"visually-hidden\">Breadcrumb</h2>\n    <ol>\n          <li>\n                  <a href=\"/\">Home</a>\n              </li>\n        </ol>\n  </nav>\n"
        }
      }
    }
  }
}

Fuentes de la arquitectura