04 / How Lupus works
Drupal outside Canvas
Decoupling does not mean discarding Drupal capabilities. It means providing supported output and connecting its behaviour to the frontend. Not every module works without adaptation.
All walkthroughs ↗Entities and view modes / Working here
The full page and teasers use CE displays for the same node. Fields and formatters remain configured in Drupal; the frontend implements ntt-insight and ntt-insight-card.
Views / Working here
lupus_decoupled_views adds the CE page display. It reuses selection, filters, view modes and the pager. This demo tests pages, not every plugin, AJAX behaviour or Views embedded in Canvas.
Blocks and module routes / New working example
ntt_lupus_examples returns a CustomElement from a compatible PHP controller. lupus_decoupled_block includes visible blocks by region in the blocks property. We placed a configurable block on that route only; Next.js presents the content region. Header and navigation stay in the Next.js shell: other regions are inspectable, not duplicated on the website.
Form API / Documented support, partial Next integration
lupus_decoupled_form can wrap Form API HTML in drupal-form--* elements. Drupal keeps validation and processing. The frontend must forward POST, cookies and tokens, handle errors, messages and redirects, and supply CSS/JS. Nuxt has middleware; our Next adapter does not implement that generic cycle. Our login and recovery screens are custom React with direct POST to Drupal, not a generic CE Form API test.
Webform, Layout Builder and other modules / Evaluate
Submodules exist for Webform and Layout Builder. Webform is not installed here and its JS libraries do not automatically reach the frontend; complex widgets need adaptation. Layout Builder needs blocks with supported CE output. A business module can expose a CE route or data to a component, but its PHP, Twig and JS behaviours do not convert themselves.
Real example from this installation
Below is the block actually received in blocks.content. Change its message in block administration, save and reload the example: Drupal remains the text source.
blocks.content.ntt_delivery_note
Inspect the contract received on the server
{
"title": "Drupal module / CE",
"content_format": "json",
"content": {
"element": "ntt-module-example",
"props": {
"title": "A Drupal module, rendered in Next.js",
"description": "This route returns a CustomElement from a PHP controller. It is not a Canvas page or a node. Drupal resolves access and adds the configured block; React renders both.",
"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": "A reusable Drupal block",
"description": "This message comes from a block configured in Drupal, not Canvas or hardcoded React text."
}
}
},
"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"
}
}
}
}
}