Skip to content

SEO: robust per-page metadata — https OG/canonical, descriptions, ORCID/Scholar, JSON-LD (#1236, #1142, #1324)#1330

Merged
jonfroehlich merged 6 commits into
masterfrom
1236-robust-opengraph-canonical-meta
Jun 18, 2026
Merged

SEO: robust per-page metadata — https OG/canonical, descriptions, ORCID/Scholar, JSON-LD (#1236, #1142, #1324)#1330
jonfroehlich merged 6 commits into
masterfrom
1236-robust-opengraph-canonical-meta

Conversation

@jonfroehlich

Copy link
Copy Markdown
Member

Overhauls on-page SEO and social-sharing metadata across the site. One cohesive
effort built on a shared page_meta infrastructure, so the commits are kept in
a single PR. Grew out of the planning doc docs/plans/issue-1142-1324-seo.md.

Closes #1236. Substantially addresses #1142 (indexing) and #1324 (metadata audit).

What changed (commit by commit)

  1. Centralized <head> metadata + fix Project page social media (opengraph) not working perfectly #1236 — replaced the duplicated
    per-template Open Graph blocks with a single source of truth in base.html
    driven by an optional page_meta context dict. Added self-referential
    canonical links, a complete Open Graph block (og:site_name,
    og:locale, og:image:width/height/alt, …), and Twitter Cards (previously
    absent). A new site_scheme context processor pins https outside DEBUG, so
    og:url/og:image/canonical no longer advertise http:// behind UW CSE's
    TLS-terminating Apache proxy — the root cause of Project page social media (opengraph) not working perfectly #1236.
  2. Per-page meta descriptions + og:titles for People / Projects /
    Publications / Awards / News (every page previously shipped the same generic
    description — a classic "Crawled – currently not indexed" driver).
  3. Person ORCID + Google Scholar fields (model + admin + member page,
    rendered with academicons). Also fixes two latent bugs found in the same
    surfaces: has_website_links() ignored bluesky/mastodon/threads/linkedin, and
    the admin "Socials" fieldset was missing bluesky.
  4. JSON-LD structured dataOrganization (home), Person with sameAs
    linking ORCID/Scholar/socials (member), NewsArticle (news). A render_jsonld()
    helper escapes </>/& so a value containing literal </script> can't
    break out of the tag.
  5. Docs: link the in-app https workaround to the IT follow-up issue Enable SECURE_PROXY_SSL_HEADER so Django sees the real https scheme (supersedes in-app workaround) #1329.

Canonical URL note

Project canonical is singular /project/<short_name>/, built with the same
reverse('website:project', …) the sitemap uses, so canonical and sitemap are
byte-for-byte identical and the /projects/ alias collapses to one indexable URL.

Follow-up (not in this PR)

Testing

  • website/tests/test_page_metadata.py (new): canonical/OG/Twitter presence, the
    Project page social media (opengraph) not working perfectly #1236 https behavior via DEBUG toggling, per-page descriptions, JSON-LD validity
    (json.loads) + the </script> breakout guard.
  • MemberSocialLinkTests in test_views.py: ORCID/Scholar rendering +
    has_website_links().
  • Full suite: 265 pass, 1 skipped (--settings=makeabilitylab.settings_test).
  • Migration 0036_person_… is gitignored per repo design; servers regenerate it
    on container start.

⚠️ Reviewer notes / verification

🤖 Generated with Claude Code

jonfroehlich and others added 6 commits June 18, 2026 05:57
…xy (#1236)

Replace the duplicated, per-template Open Graph blocks with a single source of
truth in base.html driven by an optional `page_meta` context dict (title,
description, og_type, canonical_path) with lab-wide defaults. Every page now
emits a self-referential canonical link, a complete Open Graph block
(og:site_name, og:locale, og:image:width/height/alt, …), and Twitter Card tags
(previously absent) — with the meta description computed once and reused.

Fixes #1236: absolute URLs (og:url, og:image, canonical) were built from
request.scheme, which is http behind UW CSE's TLS-terminating Apache proxy, so
social/canonical URLs advertised http://. A new `site_scheme` context processor
pins https when not DEBUG (local dev stays http). The cleaner long-term fix
(SECURE_PROXY_SSL_HEADER) is left to a separate IT-facing issue.

Detail views (project/member/news) populate page_meta and the templates override
only the share image + structural tags (og:profile:*, og:article:*). The project
canonical is built with reverse('website:project', …) so it is byte-for-byte
identical to the URL website/sitemaps.py advertises (singular /project/<name>/),
collapsing the /projects/ alias to one indexable URL.

Lays the groundwork for #1142 (indexing) and #1324 (metadata audit).

Adds website/tests/test_page_metadata.py (canonical/OG/Twitter presence, the
#1236 https behavior via DEBUG toggling, and per-page title/description/type).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g pages (#1142, #1324)

Every page previously shipped the same generic <meta name="description">, a
classic driver of Google's "Crawled - currently not indexed". Give the People,
Projects, Publications, Awards, and News listing pages each a hand-written,
distinct description and og:title via the page_meta context dict introduced in
the prior commit. (The home page already uses the canonical lab description, so
it's left on the default.)

Extends website/tests/test_page_metadata.py to pin each listing page's distinct
description/og:title and that none falls back to the generic default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… pages (#1324)

Add `orcid` and `google_scholar` URLField(blank, null) to Person, expose them
in the admin "Socials" fieldset, and render them on the member page as
academicons icons (ai-orcid / ai-google-scholar; academicons is already loaded
in base.html) following the existing per-link a11y pattern (aria-label + text).
These also enrich the Person JSON-LD `sameAs` planned in the next commit.

While here, fix two latent gaps in the same surfaces:
- has_website_links() only checked personal_website/github/twitter, so a person
  whose only link was bluesky/mastodon/threads/linkedin (and now orcid/scholar)
  had their entire profile-links nav hidden. It now returns True for any link.
- the admin "Socials" fieldset was missing `bluesky`, so it couldn't be edited
  even though the field existed and rendered on the page.

Migrations are gitignored / generated at container start, so no migration file
is committed; the test DB is built from models via the settings_test shim.

Adds MemberSocialLinkTests to website/tests/test_views.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#1324)

Emit structured data on the highest-value page types:
- Organization on the home page (name, url, logo, description,
  parentOrganization = Allen School/UW, sameAs = lab social profiles) to help
  Google build a knowledge panel for "Makeability Lab".
- Person on member pages, with sameAs linking the profile to the person's
  external identities (ORCID, Google Scholar, GitHub, …) — strengthens
  "<name> Makeability Lab" queries.
- NewsArticle on news detail (headline, datePublished, author, publisher).

Dicts are built in the views and serialized by a new render_jsonld() helper
that escapes <, >, & as \uXXXX so a value containing literal "</script>" can't
break out of the tag (covered by a regression test). base.html renders the
optional `jsonld` context var inside <script type="application/ld+json">, so a
view opts in just by setting context['jsonld'] — no per-template override.

Adds JsonLdTests (presence + json.loads validity + the breakout guard) to
website/tests/test_page_metadata.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reference #1329 (enable SECURE_PROXY_SSL_HEADER) from the in-app https
workaround so the temporary code points at the durable fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…text examples

Replace the placeholder ORCID/Scholar example URLs in the Person admin help_text
with Jon Froehlich's actual profiles so editors have a concrete, working example
to mirror. (& encoded as &amp; in the Scholar URL for valid help_text HTML.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jonfroehlich

Copy link
Copy Markdown
Member Author

Here's the after screenshot with GScholar and ORCID.

image

@jonfroehlich jonfroehlich merged commit d018673 into master Jun 18, 2026
2 checks passed
jonfroehlich added a commit that referenced this pull request Jun 18, 2026
…1142/#1324)

Marks the merged SEO metadata work (PR #1330) as a release and re-triggers the
test deploy. The version is surfaced in the /admin header, giving a quick visual
confirmation of what's live on -test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jonfroehlich jonfroehlich deleted the 1236-robust-opengraph-canonical-meta branch June 22, 2026 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Project page social media (opengraph) not working perfectly

1 participant