Site definitions generate 404 with subdomain

To change menu entries, we use this component code:

public function menuItems() {
        if ($this->menuItems !== null) {
            return $this->menuItems;
        }

        if (!strlen($this->property('code'))) {
            return;
        }

        $theme = Theme::getActiveTheme();
        $menu = PagesMenu::loadCached($theme, $this->property('code'));

        if ($menu) {
            $this->menuItems = $menu->generateReferences($this->page);
            $this->menuName = $menu->name;
        }

        $site = \Site::getActiveSite();

        $iterator = function($items) use ($site, &$iterator) {
            foreach ($items as $menuItem) {
                if ($menuItem->type === 'cms-page' && $menuItem->code === 'menu_year') {
                    $menuItem->title .= ' ' . $site->name;
                }
        
                if (!empty($menuItem->items)) {
                    $iterator($menuItem->items);
                }
            }
        };

        $iterator($this->menuItems);

        return $this->menuItems;
    }

When the subdomain (e.g. https://jahresbericht2025.mathilde-escher.ch) is defined it works great. When a subdomain is entered, where the site definition isn’t set, there is a 404, which is good. The only problem is, that the navigation is then using the missing subdomain on all menu items.

How can we fix this?

Hey @mathilde

When no site definition matches the subdomain, October returns the 404 before any site is made active. The 404 page then uses the primary site’s data, but builds links from the hostname the visitor typed. The default link policy (detect) always uses the current hostname, so every menu link gets the unknown subdomain.

There are two ways to fix this.

Option 1: force links to use the configured URL

If each site definition has Use custom application URL set (for example https://jahresbericht2025.mathilde-escher.ch), add this to your .env:

APP_URL=https://www.mathilde-escher.ch
LINK_POLICY=force

With force, links are always built from app.url and never from the visitor’s hostname. When a site matches, its custom URL replaces app.url, so its links stay on its own subdomain. An unknown subdomain matches no site, so its 404 page links to APP_URL.

Before you switch, make sure every subdomain site has a custom URL. A site matched only by “Restrict to hostnames” without a custom URL would otherwise link to APP_URL. Asset URLs follow the same rule.

Option 2: skip the menu when no site matched

Site::getActiveSiteId() is null when the hostname didn’t match any site, so you can return early in your component:

public function menuItems()
{
    if ($this->menuItems !== null) {
        return $this->menuItems;
    }

    if (!\Site::getActiveSiteId()) {
        return $this->menuItems = [];
    }

    // ...
}

The 404 page then shows no navigation. This also stops the menu title from getting the primary site’s name added to it.

1 Like