Appearance
Layout XML
Lynx pages still go through Magento's layout system: handles, layout files, page config, blocks. What changes is which files are read and what the layout is used for. On a Lynx page the visible content is built from components, so layout XML keeps the jobs around it: the <head>, page config, cache behavior and the mount point.
Everything on this page applies only to store views that run a Lynx theme or a child of one.
The lynx_ prefix
Two things happen on every Lynx request:
- Only files named
lynx_*.xmlare collected. Magento normally collects every*.xmlin thelayout/folders of modules and themes. On a Lynx store view the collector looks forlynx_*.xmlonly, sodefault.xml,catalog_product_view.xmland the layout files of other extensions are not read at all. - Every handle gets a
lynx_twin. Before the layout loads, Lynx addslynx_<handle>next to each handle of the request. A product page with the handlesdefault,catalog_product_viewandcatalog_product_view_type_simplealso getslynx_default,lynx_catalog_product_viewandlynx_catalog_product_view_type_simple.
So a file applies to the same pages as its standard counterpart, once you add the prefix:
| Standard Magento file | On a Lynx store view |
|---|---|
default.xml | lynx_default.xml |
catalog_product_view.xml | lynx_catalog_product_view.xml |
catalog_category_view.xml | lynx_catalog_category_view.xml |
cms_index_index.xml | lynx_cms_index_index.xml |
customer_account_index.xml | lynx_customer_account_index.xml |
vendor_faq_index_index.xml (your route) | lynx_vendor_faq_index_index.xml |
Files go in the usual places: view/frontend/layout/ of a module, <Vendor_Module>/layout/ of a theme. Theme files merge with module files and override them through layout/override/ exactly as in standard Magento.
The page layout is always lynx_column, set by Lynx before the layout loads, whatever the page or the files say.
Do not set a page layout in a lynx_ file
Leave out the layout="…" attribute of <page>. A page layout declared in a layout file is still picked up when Lynx answers an Inertia visit, and the visit then gets a full HTML page instead of JSON. After removing such an attribute, clean the layout cache: it keeps the merged files.
The page structure
lynx_column and OOCS_LynxTheme's lynx_default.xml give every Lynx page this skeleton:
text
<head> title, meta, links from page config and lynx_*.xml
head.additional block for extra <head> output (Open Graph…)
<body id="html-body">
lynx.after.body.start store config for the client, notices
lynx.page.wrapper
lynx.main.container
lynx.inertia the mount point: page JSON, SSR markup when on
lynx.before.body.end scripts at the end of the bodyThe storefront itself (header, content, footer) is drawn by the page component inside lynx.inertia. There is no page.wrapper, main, sidebar.main, content or footer container to reference: those belong to Magento's standard page layouts.
Blocks and templates
Blocks and .phtml templates declared in a lynx_*.xml file work as in standard Magento: they are created, their view models and plugins run, and their HTML is printed in the container you put them in. The difference is how little there is around them: Lynx's page layout has only the containers above, and Lynx declares very few blocks of its own (the store config, notices, the mount point, a handful of headless blocks for meta and cache tags). The storefront you see is the page component, not a tree of blocks.
What to keep in mind when you add one:
- Pick a Lynx container.
head.additional,lynx.after.body.start,lynx.main.container(next to the mount point) orlynx.before.body.end. Containers of Magento's standard page layouts (content,sidebar.main,product.info.main…) do not exist. - Its HTML sits outside the app. It is printed on a full page load and stays in the page while the shopper navigates; the page component around it is redrawn, the block is not.
- It is not re-rendered on Inertia visits. The layout of the new page is still built, so
_prepareLayout()runs, but only the output oflynx.inertiais sent.
So a block suits markup that is the same on every page or only matters on the first load (a verification tag, a noscript fallback, a script loader). Content that differs per page, that merchants place, or that must update as shoppers navigate belongs in a component.
What layout XML is for on Lynx
Page title and meta
Set the title, meta and robots of a page as usual. They are printed in <head> on a full page load, and the title travels with the JSON of an Inertia visit too.
xml
<!-- view/frontend/layout/lynx_vendor_faq_index_index.xml -->
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<head>
<title>Frequently Asked Questions</title>
<meta name="description" content="Answers about orders, shipping and returns"/>
<meta name="robots" content="INDEX,FOLLOW"/>
</head>
</page>Titles and meta set from PHP (a controller calling $page->getConfig()->getTitle()->set(), an observer, a block's _prepareLayout()) work the same way.
Headless blocks that set page config
When the meta of a page comes from an entity, add a block that renders nothing and fills the page config in _prepareLayout(). Lynx does this for categories: lynx_catalog_category_view.xml adds OOCS\LynxCatalog\Block\CategoryMeta to lynx.main.container, which sets the title, description, keywords and canonical link of the category. The same pattern adds full page cache tags (OOCS\LynxCatalog\Block\ProductCacheTags on product pages).
xml
<referenceContainer name="lynx.main.container">
<!-- Renders no HTML; sets title/meta/canonical in _prepareLayout() -->
<block class="Vendor\Faq\Block\QuestionMeta" name="vendor.faq.question.meta"/>
</referenceContainer>The block's _toHtml() returns an empty string. Layout generation still runs on Inertia visits, so what it sets in the page config reaches the JSON.
Extra <head> output
Add a template block to head.additional for markup the page config cannot express, as Lynx does for the product Open Graph tags in lynx_catalog_product_view.xml:
xml
<referenceBlock name="head.additional">
<block class="Magento\Framework\View\Element\Template" name="vendor.faq.schema"
template="Vendor_Faq::head/schema.phtml"/>
</referenceBlock><head> attributes (<attribute name="prefix" value="og: …"/>), <css>, <link> and <script> entries also work.
Scripts and markup outside the app
lynx.after.body.start and lynx.before.body.end take blocks that print markup at the start or end of <body>, such as a tag manager snippet. Prefer a component in the Backstage zone for tracking (see Forms and Analytics): it is placed per design in the editor and follows client-side navigation.
Page behavior
- Keep a page out of the full page cache:
<update handle="lynx_no_cacheable"/>. Lynx uses it for the checkout and the PayPal review page. - Arguments of the mount point (
ssr,encrypt_history,clear_history,sri): set them on thelynx.inertiablock. See Pages and Controllers.
What does not work
| In layout XML | On a Lynx page | Instead |
|---|---|---|
Blocks placed in containers of standard page layouts (content, sidebar.main, product.info.main…) | Those containers do not exist in lynx_column | One of the Lynx containers, or a component placed in the editor |
<referenceBlock> / <move> on Luma blocks (product.info.price, catalog.leftnav…) | Those blocks are never declared | A component, or a theme override of the Lynx component |
<head> assets that one page needs (<script src> in lynx_catalog_product_view.xml) | Loaded only when that page is the first one opened; a visit from another page does not load them | Import the code from the component that needs it |
| Widgets placed through Content › Widgets layout updates | Skipped | A component |
Blocks declared only in regular layout files, and plugins or observers on their output (toHtml, *_html_after) | The files are not read, so the blocks are never created and those hooks never run | Declare the block in a lynx_ file, or hook into the data the component's provider reads |
On Inertia visits
When the shopper moves between pages, Lynx still builds the layout of the new page, so handles, page config and _prepareLayout() run as usual. Before rendering it removes every block except lynx.inertia and answers with JSON. Of the <head>, only the title and the body classes are sent with that JSON and applied in the browser. Meta tags, canonical links and Open Graph tags are printed on full page loads, which is what search engines and link previews receive.
Adapting an extension
An extension that adds head markup or a script through default.xml works on Lynx once the same instructions are in lynx_default.xml:
xml
<!-- Vendor_Module: view/frontend/layout/lynx_default.xml -->
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="head.additional">
<block class="Vendor\Module\Block\Verification" name="vendor.module.verification"
template="Vendor_Module::verification.phtml"/>
</referenceBlock>
</body>
</page>Copy only what belongs in <head>, the page config or the body edges. Anything the extension renders inside the page becomes a component. For the rest of the differences, see Differences from Standard Magento.
After adding or changing a lynx_*.xml file, run bin/magento cache:clean layout full_page.