01 / How Lupus works
What each layer does
Lupus does not replace Drupal content modelling, queries or permissions. It organises decoupled delivery using Custom Elements and a page renderer.
All walkthroughs ↗Drupal
Entities, fields, view modes, Views, blocks, routes and access remain in Drupal. CE displays decide what to present from an entity; the frontend need not discover its fields and reconstruct each page from scratch.
Custom Elements
It is the presentation model: an element name, props for values and slots for filtered HTML or other elements. It provides entity/view-mode displays, formatters, processors, serialisation and cache metadata. It does not imply Shadow DOM or require browser Web Components.
Lupus CE Renderer
Transforms supported main content into a page response with title, content and metadata. It can include messages, breadcrumbs or local tasks. It does not automatically convert any HTML route: the route and its output must support this format.
Lupus Decoupled + CE API
Lupus packages the integrations. lupus_decoupled_ce_api activates the renderer for /ce-api requests and integrates frontend redirects and absolute file URLs. Submodules add specific support for Canvas, Views, blocks or forms. “Lupus CE” is not a second independent data API.
Next.js / React
Our adapter validates JSON and maps CE names to known React components. It traverses slots, generates server HTML and adds interaction where needed. This adapter is POC development, not an automatic Next.js SDK supplied by Lupus.
Real example from this installation
Inspect the insight: ntt-insight has title and href props and its filtered body in slots.body. The same entity appears as ntt-insight-card in the View.
Inspect the contract received on the server
{
"title": "Digital experiences: from content to channel",
"content_format": "json",
"content": {
"element": "ntt-insight",
"props": {
"href": "/en/demo/digital-experience",
"title": "Digital experiences: from content to channel"
},
"slots": {
"body": "<p>A portal keeping content, queries and blocks in Drupal while presenting a Next.js experience through shared CE delivery.</p><h2>One portal, different editorial needs</h2><p>A campaign needs composition freedom. An insight needs clear structure. A listing needs to select published content. Each need keeps its tool and shares the same frontend.</p><h2>An editorial decision, not a visual dependency</h2><p>This page has no Canvas tree or template. Title, body and summary use the classic editing form. Drupal CE displays define the full-page and teaser output; React implements both presentations.</p><h2>What Drupal keeps</h2><p>Drupal keeps fields, formatters, permissions, translations and view modes. Lupus CE Renderer delivers routed content with metadata; CE API exposes it under /ce-api. Next.js validates the contract and renders known elements.</p><h2>What you can try</h2><p>Open this insight and its teaser in Insights. Inspect both responses. React does not rebuild field selection or the View query, although components and interactions remain frontend work.</p><p><a href=\"/en/demo/guide/layers\">Explore the explanation and real example</a></p>"
}
},
"metatags": {
"meta": [
{
"name": "title",
"content": "Digital experiences: from content to channel | Canvas Lupus POC"
},
{
"name": "description",
"content": "A portal keeping content, queries and blocks in Drupal while presenting a Next.js experience through shared CE delivery."
}
],
"link": [
{
"rel": "canonical",
"href": "https://dev-headless-lupus.pantheonsite.io/en/demo/digital-experience"
},
{
"rel": "alternate",
"hreflang": "en",
"href": "https://dev-headless-lupus.pantheonsite.io/en/demo/digital-experience"
},
{
"rel": "alternate",
"hreflang": "es",
"href": "https://dev-headless-lupus.pantheonsite.io/demo/digital-experience"
}
]
},
"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>"
}
}
},
"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=\"/en\" data-drupal-link-system-path=\"<front>\">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=\"/en/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=\"/en\">Home</a>\n </li>\n </ol>\n </nav>\n"
}
}
}
}
}