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

Creating the Layout XML and Controller for the New Page

Creating the Layout XML and Controller for the New Page

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

In this chapter, we get the team page running for the very first time - still without real content, but reachable at the target URL /team. That takes four files: module registration, route, controller, and an (empty) Layout XML.

Module scaffolding

app/code/Mironsoft/TeamPage/registration.php
<?php

declare(strict_types=1);

use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Mironsoft_TeamPage',
    __DIR__
);
app/code/Mironsoft/TeamPage/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Mironsoft_TeamPage" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Theme"/>
        </sequence>
    </module>
</config>

The route: routes.xml

etc/frontend/routes.xml registers the front name team for the frontend and ties it to our module:

app/code/Mironsoft/TeamPage/etc/frontend/routes.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="team" frontName="team">
            <module name="Mironsoft_TeamPage"/>
        </route>
    </router>
</config>

The controller: Controller/Index/Index.php

The folder name Index under Controller/ matches the URL segment after the front name, and the class name Index matches the action - together that gives /team/index/index, which Magento automatically shortens to /team (default controller/default action). The controller uses constructor property promotion and just serves the page - data will come later via the ViewModel, not through the controller itself:

app/code/Mironsoft/TeamPage/Controller/Index/Index.php
<?php

declare(strict_types=1);

namespace Mironsoft\TeamPage\Controller\Index;

use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\Page;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;

/**
 * Serves the team overview page at /team.
 */
class Index implements HttpGetActionInterface
{
    /**
     * @param ResultFactory $resultFactory Creates the page result for the team page.
     */
    public function __construct(
        private readonly ResultFactory $resultFactory,
    ) {
    }

    /**
     * Renders the team page via the team_index_index layout handle.
     *
     * @return ResultInterface|ResponseInterface
     */
    public function execute(): ResultInterface|ResponseInterface
    {
        /** @var Page $resultPage */
        $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE);
        $resultPage->getConfig()->getTitle()->set('Our Team');

        return $resultPage;
    }
}

HttpGetActionInterface, instead of the older, more general Action base class, makes it explicit that this controller only serves GET requests - the right choice for a pure display page with no form submission.

A first, empty layout handle

So the page returns a valid response at all (instead of an empty default layout), we already create the layout handle now - for the moment with a simple placeholder block that we'll replace with the real template in chapter 20:

app/code/Mironsoft/TeamPage/view/frontend/layout/team_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">
    <body>
        <referenceContainer name="content">
            <block class="Magento\Framework\View\Element\Template"
                   name="team.page"
                   template="Mironsoft_TeamPage::team/index.phtml"/>
        </referenceContainer>
    </body>
</page>

Activating and testing the module

bin/magento module:enable Mironsoft_TeamPage
bin/magento setup:upgrade
bin/cache-clean

Achtung: At this point, the template team/index.phtml doesn't exist yet (that comes in chapter 20) - so visiting /team right now would throw a "Template file ... does not exist" error. That's expected at this stage; chapters 19 and 20 close that gap.