feat: add canonical redirect, og_image, next_page_url, and sitemap caching across content routers

Implement canonical_redirect helper in content.py to enforce slug-based 301 redirects for gist, news, post, and project detail pages. Add first_image_url helper to extract primary image from item or attachments for Open Graph meta tags. Inject next_page_url into feed, gists, news, and projects list page SEO contexts for pagination link rel. Extend seo.py with SITEMAP_URL_LIMIT, SITEMAP_TTL constants and _sitemap cache variable for future sitemap generation optimization. Update profile page to raise 404 via not_found instead of 302 redirect, and include og_image from avatar_url.
This commit is contained in:
2026-06-05 18:05:07 +00:00
parent 9a24a90ec5
commit f05b6a4faf
14 changed files with 326 additions and 23 deletions
+7 -2
View File
@@ -4,10 +4,10 @@ from fastapi import APIRouter, Request, Form
from devplacepy.models import GistForm, GistEditForm
from fastapi.responses import HTMLResponse, RedirectResponse
from devplacepy.database import get_table, get_users_by_uids, get_gist_languages, paginate
from devplacepy.content import load_detail, edit_content_item, delete_content_item, enrich_items, create_content_item, detail_context
from devplacepy.content import load_detail, edit_content_item, delete_content_item, enrich_items, create_content_item, detail_context, canonical_redirect, first_image_url
from devplacepy.templating import templates
from devplacepy.utils import get_current_user, require_user, not_found, XP_GIST
from devplacepy.seo import base_seo_context, site_url, website_schema, software_source_code_schema, list_page_seo
from devplacepy.seo import base_seo_context, site_url, website_schema, software_source_code_schema, list_page_seo, next_page_url
logger = logging.getLogger(__name__)
router = APIRouter()
@@ -53,6 +53,7 @@ async def gists_page(request: Request, language: str = None, user_uid: str = Non
{"name": "Home", "url": "/feed"},
{"name": "Gists", "url": "/gists"},
],
next_url=next_page_url(request, next_cursor),
)
return templates.TemplateResponse(request, "gists.html", {
**seo_ctx,
@@ -74,6 +75,9 @@ async def gist_detail(request: Request, gist_slug: str):
if not detail:
raise not_found("Gist not found")
gist = detail["item"]
redirect = canonical_redirect("gists", gist, gist_slug)
if redirect:
return redirect
base = site_url(request)
seo_ctx = base_seo_context(
@@ -81,6 +85,7 @@ async def gist_detail(request: Request, gist_slug: str):
title=gist.get("title", "Gist"),
description=gist.get("description", "")[:160],
og_type="article",
og_image=first_image_url(gist, detail["attachments"]),
breadcrumbs=[
{"name": "Home", "url": "/feed"},
{"name": "Gists", "url": "/gists"},