Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Layout XML for the New Pages

Layout XML for the New Pages

~7 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

Three controllers in this block render a full page: History\Index (chapter 45), Catalog\Index, and Catalog\View (chapter 49). Each needs a layout XML file whose name follows from the route ID, controller, and action name - {route_id}_{controller}_{action}, where route_id here is mironsoft_loyalty, the internal frontName value from chapter 45, independent of the pretty store URL from chapter 46.

New layout handles from block 6

app/code/Mironsoft/Loyalty/view/frontend/layout/
├── mironsoft_loyalty_history_index.xml   # History\Index
├── mironsoft_loyalty_catalog_index.xml    # Catalog\Index
└── mironsoft_loyalty_catalog_view.xml     # Catalog\View
                                            # (NO file for redeem_index - see below)

history_index: with account navigation

<update handle="customer_account"/> automatically pulls in the complete "My Account" page structure, sidebar navigation included - no markup of your own needed for that, the same handle every core "My Account" subpage (orders, addresses, ...) references.

app/code/Mironsoft/Loyalty/view/frontend/layout/mironsoft_loyalty_history_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"
      layout="2columns-left">
    <update handle="customer_account"/>
    <body>
        <referenceBlock name="page.main.title">
            <arguments>
                <argument name="page_title" xsi:type="string" translate="true">My Points History</argument>
            </arguments>
        </referenceBlock>
        <referenceContainer name="content">
            <block class="Magento\Framework\View\Element\Template"
                   name="loyalty.history"
                   template="Mironsoft_Loyalty::history/index.phtml">
                <arguments>
                    <argument name="view_model" xsi:type="object">Mironsoft\Loyalty\ViewModel\PointsHistory</argument>
                </arguments>
            </block>
        </referenceContainer>
    </body>
</page>

Mironsoft\Loyalty\ViewModel\PointsHistory is one more small view model following the same pattern as PointsBalance (chapter 48): it injects CustomerSession and PointsLedgerRepositoryInterface (chapter 6) and exposes getEntries(): array (PointsLedgerInterface[]) to the template. The code follows exactly the pattern shown in chapter 48 and isn't repeated here.

catalog_index and catalog_view

app/code/Mironsoft/Loyalty/view/frontend/layout/mironsoft_loyalty_catalog_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"
      layout="1column">
    <body>
        <referenceContainer name="content">
            <block class="Magento\Framework\View\Element\Template"
                   name="loyalty.catalog.list"
                   template="Mironsoft_Loyalty::catalog/index.phtml">
                <arguments>
                    <argument name="view_model" xsi:type="object">Mironsoft\Loyalty\ViewModel\RewardCatalog</argument>
                </arguments>
            </block>
        </referenceContainer>
    </body>
</page>
app/code/Mironsoft/Loyalty/view/frontend/layout/mironsoft_loyalty_catalog_view.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"
      layout="1column">
    <body>
        <referenceContainer name="content">
            <block class="Magento\Framework\View\Element\Template"
                   name="loyalty.catalog.detail"
                   template="Mironsoft_Loyalty::catalog/view.phtml">
                <arguments>
                    <argument name="view_model" xsi:type="object">Mironsoft\Loyalty\ViewModel\RewardDetail</argument>
                </arguments>
            </block>
        </referenceContainer>
    </body>
</page>

Both deliberately use layout="1column" instead of 2columns-left: unlike the history page, the catalog and detail pages are meant for guests too (chapter 49) and don't conceptually belong to "My Account", so they don't need the account sidebar.

Why there's no redeem_index.xml

Achtung: Redeem\Index (chapter 50) never renders a page - every code path in execute() ends with a Redirect result, never a Page result. Adding a layout file for the handle mironsoft_loyalty_redeem_index wouldn't just be superfluous, it would signal a misunderstanding: POST actions that change something should, as a rule, always end in a redirect rather than rendered HTML - otherwise resubmitting via the browser's "back" button (the infamous "resubmit form?" problem) would redeem the reward a second time. The same rule applies to every write action in Magento, not just this module.

Tipp: bin/magento setup:upgrade is not required for new layout XML files - layout is read from the filesystem at runtime, not maintained in the database. A plain bin/cache-clean layout is enough to make a new or changed file visible (see the deploy sequence in CLAUDE.md, which prescribes an extra static content step for CSS changes - that step is unnecessary for pure layout XML changes).