Skip to content

Headless site URLs are treated as external in URL::tidy() #15232

Description

@lostgeek

Bug description

In a headless multisite setup, the CMS and public frontend run on different hosts.

Entry permalinks and SEO metadata are generated for the frontend host. For language/site root URLs (e.g. /, /fr), those URLs often include a trailing slash (https://www.example.com/fr/).

We expect Statamic’s default trailing-slash policy (no trailing slash unless URL::enforceTrailingSlashes() is enabled) to apply to those URLs as well.

What actually happens: URL::tidy() leaves them unchanged. Root URLs keep their trailing slash in canonical URLs, hreflang alternates, sitemap entries, etc.

Example:

Field Value
home_url https://www.example.com/fr
canonical_url / permalink https://www.example.com/fr/

home_url is correct because it comes from Site::absoluteUrl(), which resolves site config properly. Entry permalinks go through URL::tidy() without normalization.

How to reproduce

Setup

  1. Configure split hosts:
APP_URL=https://cms.example.com
FRONTEND_URL=https://www.example.com
  1. In resources/sites.yaml:
de:
  url: '{{ config.app.frontend_url }}'
fr:
  url: '{{ config.app.frontend_url }}/fr'
  1. Create/publish homepage entries for de and fr (URI / on each site).

Steps

  1. Open Tinker or a test route on the CMS.
  2. Resolve a frontend root URL, e.g. the FR homepage permalink:
use Statamic\Facades\Entry;
use Statamic\Facades\URL;

$entry = Entry::query()->where('site', 'fr')->where('uri', '/')->first();

$permalink = $entry->absoluteUrl(); // e.g. https://www.example.com/fr/
  1. Compare tidy behavior:
URL::tidy($permalink);        // https://www.example.com/fr/  ← trailing slash kept
URL::tidy($permalink, true);  // https://www.example.com/fr   ← normalized

Logs

Environment

Environment
Laravel Version: 12.66.0
PHP Version: 8.5.6
Composer Version: 2.10.0
Environment: local
Debug Mode: ENABLED
Maintenance Mode: OFF
Timezone: UTC
Locale: de

Statamic
Addons: 7
License Key: Set
Sites: 3 (🇩🇪 Deutsch, 🇫🇷 Français, 🇬🇧 English)
Stache Watcher: Enabled (auto)
Static Caching: Disabled
Version: 6.27.1 PRO

Statamic Addons
statamic/seo-pro: 7.13.1

Installation

Other (please explain)

Additional details

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions