Skip to content
Merged

Seo #28

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
dependencies = [
"django~=5.2.12",
"django~=5.2.13",
"django-braces",
"django-debug-toolbar",
"django-environ",
Expand All @@ -18,7 +18,7 @@ description = "Spokane Python Community"
dynamic = ["version"]
keywords = ["django"]
license = {file = "LICENSE"}
name = "my_django_project"
name = "SpokanePythonCommunity"
readme = "README.md"
requires-python = ">=3.12"

Expand Down
1 change: 1 addition & 0 deletions src/django_project/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.sitemaps",
# third party apps
"django_extensions",
"django_filters",
Expand Down
5 changes: 4 additions & 1 deletion src/django_project/core/urls/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
from core.views import robots_txt
from django.conf import settings
from django.contrib import admin
from django.contrib.sitemaps.views import sitemap
from django.urls import include, path
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularRedocView,
SpectacularSwaggerView,
)
from web.sitemaps import sitemaps

urlpatterns = [
# Django provided URLs
Expand All @@ -35,8 +37,9 @@
path("rest/schema/", SpectacularAPIView.as_view(), name="schema"),
path("rest/swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger"),
path("rest/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
# project level URLs
# SEO URLs
path("robots.txt", robots_txt),
path("sitemap.xml", sitemap, {"sitemaps": sitemaps}, name="django.contrib.sitemaps.views.sitemap"),
# URLs to local apps
path("", include("web.urls", namespace="web")),
]
Expand Down
1 change: 1 addition & 0 deletions src/django_project/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ def robots_txt(request) -> HttpResponse:
Disallow: /__debug__/
Disallow: /accounts/
Disallow: /admin/
Sitemap: https://spokanepython.com/sitemap.xml
"""
19 changes: 19 additions & 0 deletions src/django_project/web/sitemaps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.contrib.sitemaps import Sitemap


class StaticViewSitemap(Sitemap):
"""Sitemap for static pages."""

priority = 1.0
changefreq = "daily"

def items(self) -> list[str]:
return ["/"]

def location(self, item: str) -> str:
return item


sitemaps = {
"static": StaticViewSitemap,
}
38 changes: 34 additions & 4 deletions src/django_project/web/templates/web/full/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,25 @@
<meta property="og:title" content="Spokane Python User Group">
<meta property="og:description"
content="Spokane's premiere local Python user group. Join us for meetups, workshops, and community events to learn and grow in Python programming.">
<meta name="robots" content="noindex, nofollow">
<meta property="og:url" content="https://spokanepython.com/">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Spokane Python User Group">
<meta property="og:image" content="https://spokanepython.com/static/spokanepython/img/favicon.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Spokane Python User Group">
<meta name="twitter:description" content="Spokane's premiere local Python user group. Join us for meetups, workshops, and community events to learn and grow in Python programming.">
<meta name="twitter:image" content="https://spokanepython.com/static/spokanepython/img/favicon.png">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://spokanepython.com/">
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'spokanepython/img/favicon.png' %}">

<title>Spokane Python User Group</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<!-- Preconnect for external font resources -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Non-blocking Font Awesome -->
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"></noscript>
<link rel="stylesheet" href="{% static 'spokanepython/css/spug.css' %}">

<script src="https://unpkg.com/htmx.org@1.9.12"></script>
Expand All @@ -37,8 +51,6 @@
background-color: #f1f1f1
}

@import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;700&display=swap');

body {
font-family: 'Ubuntu', Arial, sans-serif;
padding-top: 70px;
Expand Down Expand Up @@ -135,6 +147,24 @@
}
</style>

<!-- Structured Data: Organization -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Spokane Python User Group",
"url": "https://spokanepython.com",
"description": "Spokane's premiere local Python user group. We host monthly meetups, workshops, and community events for Python developers of all levels.",
"sameAs": [
"https://www.meetup.com/python-spokane",
"https://github.com/SpokaneTech",
"https://discord.gg/wHrSJNgpnP",
"https://www.linkedin.com/company/spokanetech/",
"https://www.facebook.com/spokanetech.org/"
]
}
</script>

</head>

<body>
Expand Down
20 changes: 20 additions & 0 deletions src/django_project/web/templates/web/partials/future_events.htm
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
{% for event in events %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Event",
"name": "{{ event.name|escapejs }}",
"startDate": "{{ event.start_date_time|date:'c' }}",
"endDate": "{{ event.end_date_time|date:'c' }}",
"organizer": {
"@type": "Organization",
"name": "Spokane Python User Group",
"url": "https://spokanepython.com"
}{% if event.location %},
"location": {
"@type": "Place",
"name": "{{ event.location|escapejs }}"
}{% endif %}{% if event.description %},
"description": "{{ event.description|escapejs }}"{% endif %}{% if event.url %},
"url": "{{ event.url|escapejs }}"{% endif %}
}
</script>
Comment on lines +2 to +21
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: This Event JSON-LD is rendered inside an HTMX-loaded partial, so it is not present in the initial HTML response and depends on client-side execution to appear. Crawlers that do not execute HTMX/JS will miss the structured data entirely, defeating the SEO/discovery goal. Render critical structured data in server-delivered HTML for the page response. [possible bug]

Severity Level: Major ⚠️
- ⚠️ Upcoming events structured data missing from main index view.
- ⚠️ Non-JS crawlers miss Event JSON-LD for /home.
- ⚠️ Event-specific discovery in search engines potentially reduced.
Steps of Reproduction ✅
1. Start the Django server and request the home page (e.g. `GET /home`), which is handled
by `IndexView` in `src/django_project/web/views/gui.py:17-19` and renders the
`web/full/index.html` template.

2. Inspect the initial HTML response of `web/full/index.html`; in the "Upcoming Events"
section at lines 253-278, the `div` with `id="futureEventsList"` uses `hx-get="{% url
'web:htmx_future_events' %}"` and `hx-trigger="load"` (lines 270-273), but there is no
`<script type="application/ld+json">` block with `"@type": "Event"` in this initial HTML.

3. Follow the HTMX endpoint configured in `src/django_project/web/urls/gui.py:13-15`,
where the route `"htmx/future-events/"` is mapped to `FutureEventsPartialView` in
`src/django_project/web/views/gui.py:70-75`, which renders the partial template
`web/partials/future_events.htm` with the `events` context.

4. Open `src/django_project/web/templates/web/partials/future_events.htm:1-37` and see
that inside the `{% for event in events %}` loop, lines 2-21 render a `<script
type="application/ld+json">` block containing the Event JSON-LD; because this template is
only fetched when the client-side HTMX script (included at `web/full/index.html:47`)
executes the `hx-get` request, crawlers that do not execute JavaScript/HTMX will never
request `/htmx/future-events/` and therefore will not see the Event structured data,
undermining the SEO/discovery goal for upcoming events.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/django_project/web/templates/web/partials/future_events.htm
**Line:** 2:21
**Comment:**
	*Possible Bug: This Event JSON-LD is rendered inside an HTMX-loaded partial, so it is not present in the initial HTML response and depends on client-side execution to appear. Crawlers that do not execute HTMX/JS will miss the structured data entirely, defeating the SEO/discovery goal. Render critical structured data in server-delivered HTML for the page response.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

<div class="card scroll-card shadow">
<div class="card-body d-flex flex-column">
<h4 class="card-title fw-bold text-primary mb-3">{{ event.name }}</h4>
Expand Down
4 changes: 2 additions & 2 deletions src/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# === STAGE 1: Build dependencies in a virtual environment ===
FROM python:3.12-alpine AS builder
FROM python:3.13-alpine AS builder

WORKDIR /app

Expand All @@ -20,7 +20,7 @@ RUN pip install .[docker]


# === STAGE 2: Runtime with only necessary files ===
FROM python:3.12-alpine
FROM python:3.13-alpine

ARG IMAGE_TAG=dev
ENV IMAGE_TAG=${IMAGE_TAG}
Expand Down
Loading