diff --git a/core/seo.py b/core/seo.py
index 9f1061f..abd2183 100644
--- a/core/seo.py
+++ b/core/seo.py
@@ -5,6 +5,7 @@ from typing import TypedDict
class SeoMeta(TypedDict, total=False):
"""Shared typed optional SEO metadata for template context generation."""
+ page_url: str | None
page_image: str | None
page_image_width: int | None
page_image_height: int | None
diff --git a/core/views.py b/core/views.py
index 2cd6d97..797a0a5 100644
--- a/core/views.py
+++ b/core/views.py
@@ -73,6 +73,7 @@ DEFAULT_SITE_DESCRIPTION = "Archive of Twitch drops, campaigns, rewards, and mor
def _build_seo_context( # noqa: PLR0913, PLR0917
page_title: str = "ttvdrops",
page_description: str | None = None,
+ page_url: str | None = None,
page_image: str | None = None,
page_image_width: int | None = None,
page_image_height: int | None = None,
@@ -89,6 +90,7 @@ def _build_seo_context( # noqa: PLR0913, PLR0917
Args:
page_title: Page title (shown in browser tab, og:title).
page_description: Page description (meta description, og:description).
+ page_url: Canonical absolute URL for the current page.
page_image: Image URL for og:image meta tag.
page_image_width: Width of the image in pixels.
page_image_height: Height of the image in pixels.
@@ -115,6 +117,8 @@ def _build_seo_context( # noqa: PLR0913, PLR0917
"og_type": og_type,
"robots_directive": robots_directive,
}
+ if page_url:
+ context["page_url"] = page_url
if page_image:
context["page_image"] = page_image
if page_image_width and page_image_height:
@@ -437,6 +441,7 @@ def docs_rss_view(request: HttpRequest) -> HttpResponse:
seo_context: dict[str, Any] = _build_seo_context(
page_title="Twitch RSS Feeds",
page_description="RSS feeds for Twitch drops.",
+ page_url=request.build_absolute_uri(reverse("core:docs_rss")),
)
return render(
request,
@@ -836,6 +841,7 @@ def search_view(request: HttpRequest) -> HttpResponse:
seo_context: dict[str, Any] = _build_seo_context(
page_title=page_title,
page_description=page_description,
+ page_url=request.build_absolute_uri(reverse("core:search")),
)
return render(
request,
diff --git a/kick/views.py b/kick/views.py
index 33066f4..f87fb08 100644
--- a/kick/views.py
+++ b/kick/views.py
@@ -56,6 +56,8 @@ def _build_seo_context(
"robots_directive": "index, follow",
}
if seo_meta:
+ if seo_meta.get("page_url"):
+ context["page_url"] = seo_meta["page_url"]
if seo_meta.get("og_type"):
context["og_type"] = seo_meta["og_type"]
if seo_meta.get("robots_directive"):
@@ -225,6 +227,7 @@ def campaign_list_view(request: HttpRequest) -> HttpResponse:
seo_context: dict[str, str] = _build_seo_context(
page_title=title,
page_description="Browse Kick drop campaigns.",
+ seo_meta={"page_url": request.build_absolute_uri(base_url)},
)
return render(
request,
diff --git a/templates/includes/meta_tags.html b/templates/includes/meta_tags.html
index 32a0b5f..a8e1797 100644
--- a/templates/includes/meta_tags.html
+++ b/templates/includes/meta_tags.html
@@ -16,9 +16,6 @@
{# - robots_directive: str - robots meta content (default: "index, follow") #}
{# #}
{% load static %}
-{# Preconnect to external resources for performance #}
-
-
{# Description meta tag #}
@@ -26,7 +23,7 @@
{# Author and Copyright #}
-
+
{# Open Graph tags for social sharing #}
@@ -45,10 +42,14 @@
{% endif %}
{# Twitter Card tags for rich previews #}
+ content="{% if page_image %}
+ summary_large_image
+ {% else %}
+ summary
+ {% endif %}" />
+ content="{% firstof page_description 'ttvdrops - Twitch and Kick drops.' %}" />
{% if page_image %}{% endif %}
{# Article dates for content pages #}
{% if published_date %}{% endif %}
@@ -61,6 +62,22 @@
{% for link in pagination_info %}{% endfor %}
{% endif %}
{# Schema.org JSON-LD structured data #}
-{% if schema_data %}{% endif %}
+{% if schema_data %}
+
+{% endif %}
{# Breadcrumb schema #}
-{% if breadcrumb_schema %}{% endif %}
+{% if breadcrumb_schema %}
+
+{% endif %}
diff --git a/twitch/views.py b/twitch/views.py
index e797cb3..b21cc71 100644
--- a/twitch/views.py
+++ b/twitch/views.py
@@ -116,6 +116,8 @@ def _build_seo_context(
"robots_directive": "index, follow",
}
if seo_meta:
+ if seo_meta.get("page_url"):
+ context["page_url"] = seo_meta["page_url"]
if seo_meta.get("og_type"):
context["og_type"] = seo_meta["og_type"]
if seo_meta.get("robots_directive"):
@@ -483,6 +485,7 @@ def drop_campaign_list_view(request: HttpRequest) -> HttpResponse: # noqa: PLR0
page_title=title,
page_description=description,
seo_meta={
+ "page_url": request.build_absolute_uri(base_url),
"pagination_info": pagination_info,
"schema_data": collection_schema,
},
@@ -1336,6 +1339,7 @@ def reward_campaign_list_view(request: HttpRequest) -> HttpResponse:
page_title=title,
page_description=description,
seo_meta={
+ "page_url": request.build_absolute_uri(base_url),
"pagination_info": pagination_info,
"schema_data": collection_schema,
},
@@ -1557,6 +1561,7 @@ class ChannelListView(ListView):
page_title="Twitch Channels",
page_description="List of Twitch channels participating in drop campaigns.",
seo_meta={
+ "page_url": self.request.build_absolute_uri(base_url),
"pagination_info": pagination_info,
"schema_data": collection_schema,
},