Skip to content

feat(landing): SSR <head> injection for blog posts (canonical + OG + JSON-LD) — GEO/SEO Phase 1.2 #2968

@matheuspoleza

Description

@matheuspoleza

Description

Wire per-page <head> injection into the landing worker: canonical URL, Open Graph tags, Twitter card, and expanded JSON-LD (BlogPosting, TechArticle, SoftwareApplication, FAQPage). Without this, every blog post published is SEO-invisible and LLMs have no structured data to parse.

Context

Part of GEO/SEO strategy v1 (28-day scope).

  • Design doc: plans/geo-seo-strategy.md
  • Full task spec: plans/geo-seo-strategy/phase-01-foundation-infra.md → Task 2
  • Technical reviewer flag: current landing is prerendered static worker (see packages/landing/src/worker.ts) — SSR head injection may require build-time prerender refactor OR moving blog to runtime SSR (breaks edge cache). Decision pending during implementation.

Acceptance Criteria

BDD scenarios

describe('Feature: SSR <head> injection for blog posts', () => {
  describe('Given a published blog post at /blog/<slug>', () => {
    describe('When the page is rendered SSR', () => {
      it('then <title> and <meta name="description"> match frontmatter', () => {});
      it('then <link rel="canonical"> is absolute and points to vertz.dev/blog/<slug>', () => {});
      it('then OG tags present: og:title, og:description, og:image, og:url, og:type', () => {});
      it('then a <script type="application/ld+json"> with BlogPosting + TechArticle is present', () => {});
      it('then <link rel="alternate" type="application/rss+xml"> references /blog/feed.xml', () => {});
    });
  });

  describe('Given the rendered HTML of one blog post', () => {
    describe('When tested at Google Rich Results Test', () => {
      it('then it passes with no errors', () => {});
    });
  });
});

Checklist

  • Canonical + OG + Twitter card + JSON-LD on every blog post
  • JSON-LD helper module extended: BlogPosting + TechArticle + SoftwareApplication + FAQPage
  • JSON-LD validates against schema.org (CI check)
  • Rich Results Test passes for 1 blog post (manual verification)
  • Lighthouse-95+ retained on blog post pages

Dependencies

Progress

  • 2026-04-22: Opened

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions