diff --git a/web/src/padelnomics/admin/templates/admin/partials/seo_scorecard.html b/web/src/padelnomics/admin/templates/admin/partials/seo_scorecard.html index 49071e8..715ce89 100644 --- a/web/src/padelnomics/admin/templates/admin/partials/seo_scorecard.html +++ b/web/src/padelnomics/admin/templates/admin/partials/seo_scorecard.html @@ -69,7 +69,7 @@ {% for a in scorecard %} - {{ a.title or a.url_path }} + {{ a.title or a.url_path }} {% if a.template_slug %}
{{ a.template_slug }} {% endif %} diff --git a/web/src/padelnomics/content/__init__.py b/web/src/padelnomics/content/__init__.py index 6b07976..f5d98cb 100644 --- a/web/src/padelnomics/content/__init__.py +++ b/web/src/padelnomics/content/__init__.py @@ -308,8 +308,8 @@ async def generate_articles( # Build render context: row data + language ctx = {**row, "language": lang} - # Render URL pattern - url_path = f"/{lang}" + _render_pattern(config["url_pattern"], ctx) + # Render URL pattern (no lang prefix — blueprint provides /) + url_path = _render_pattern(config["url_pattern"], ctx) if is_reserved_path(url_path): continue @@ -375,8 +375,8 @@ async def generate_articles( # Extract FAQ pairs for structured data faq_pairs = _extract_faq_pairs(body_md) - # Build SEO metadata - full_url = base_url + url_path + # Build SEO metadata (full_url includes lang prefix for canonical/OG) + full_url = f"{base_url}/{lang}{url_path}" publish_dt = datetime( publish_date.year, publish_date.month, publish_date.day, 8, 0, 0, @@ -397,7 +397,7 @@ async def generate_articles( ) # JSON-LD - breadcrumbs = _build_breadcrumbs(url_path, base_url) + breadcrumbs = _build_breadcrumbs(f"/{lang}{url_path}", base_url) jsonld_objects = build_jsonld( config["schema_type"], title=title, @@ -499,7 +499,7 @@ async def preview_article( ctx = {**row, "language": lang} - url_path = f"/{lang}" + _render_pattern(config["url_pattern"], ctx) + url_path = _render_pattern(config["url_pattern"], ctx) title = _render_pattern(config["title_pattern"], ctx) meta_desc = _render_pattern(config["meta_description_pattern"], ctx) diff --git a/web/src/padelnomics/content/routes.py b/web/src/padelnomics/content/routes.py index f7f669c..fcf9203 100644 --- a/web/src/padelnomics/content/routes.py +++ b/web/src/padelnomics/content/routes.py @@ -23,7 +23,7 @@ BUILD_DIR = Path("data/content/_build") RESERVED_PREFIXES = ( "/admin", "/auth", "/planner", "/billing", "/dashboard", "/directory", "/leads", "/suppliers", "/health", - "/sitemap", "/static", "/markets", "/features", "/feedback", + "/sitemap", "/static", "/features", "/feedback", ) SCENARIO_RE = re.compile(r'\[scenario:([a-z0-9_-]+)(?::([a-z]+))?\]') diff --git a/web/tests/test_content.py b/web/tests/test_content.py index bcc238f..015b3ff 100644 --- a/web/tests/test_content.py +++ b/web/tests/test_content.py @@ -322,8 +322,9 @@ class TestReservedPaths: def test_planner_reserved(self): assert is_reserved_path("/planner/") is True - def test_markets_reserved(self): - assert is_reserved_path("/markets") is True + def test_markets_not_reserved(self): + # /markets sub-paths are article URLs; explicit /markets route takes priority + assert is_reserved_path("/markets/germany/berlin") is False def test_custom_path_allowed(self): assert is_reserved_path("/padel-court-cost-miami") is False @@ -456,7 +457,7 @@ class TestGenerationPipeline: miami = await fetch_one("SELECT * FROM articles WHERE slug = 'test-city-en-miami'") assert miami is not None - assert miami["url_path"] == "/en/markets/us/miami" + assert miami["url_path"] == "/markets/us/miami" assert miami["title"] == "Padel in Miami" assert miami["template_slug"] == "test-city" assert miami["language"] == "en" @@ -761,7 +762,7 @@ class TestPreviewArticle: from padelnomics.content import preview_article result = await preview_article("test-city", "miami") assert result["title"] == "Padel in Miami" - assert result["url_path"] == "/en/markets/us/miami" + assert result["url_path"] == "/markets/us/miami" assert result["meta_description"] == "Padel costs in Miami" assert "

" in result["html"] @@ -773,7 +774,7 @@ class TestPreviewArticle: async def test_preview_with_language(self, db, pseo_env): from padelnomics.content import preview_article result = await preview_article("test-city", "miami", lang="de") - assert result["url_path"] == "/de/markets/us/miami" + assert result["url_path"] == "/markets/us/miami" async def test_preview_unknown_template_raises(self, db, pseo_env): from padelnomics.content import preview_article