New Feature·Pushed May 1, 2026·L
SEO layer added across all public routes
Gitpulse now ships proper SEO metadata on every page: canonical URLs, OpenGraph images, Twitter cards, JSON-LD structured data, robots.txt, and an XML sitemap. Stories should surface better in search results and share attractively on social platforms.
Gitpulse stories can now be shared on social media with rich preview cards and indexed by search engines without friction. Every public route — the homepage and individual story pages — now includes canonical URLs, OpenGraph metadata with generated 1200×630 images, Twitter card tags, and JSON-LD structured data. The homepage renders a WebSite schema block, while individual stories output NewsArticle schemas with author, publication date, category, and word count. An XML sitemap and robots.txt file round out the discoverability layer.
The SEO utilities live in a new [[code]]lib/seo.ts[[/code]] module that centralizes URL derivation (from [[code]]GITPULSE_SITE_URL[[/code]] or [[code]]GITHUB_REPOSITORY[[/code]]), canonical URL construction, and metadata builders. This replaces inline metadata scattered across page components.
OpenGraph images are generated at build time using Next.js [[code]]ImageResponse[[/code]]. Story cards display the headline, standfirst, category, author, and size assessment — all truncated to fit the 1200×630 canvas. The root OG image shows the publication name and subtitle with a gold accent bar.
In the [[code]]@gitpulse/site[[/code]] app, routes that support static export declare [[code]]dynamic = 'force-static'[[/code]] to satisfy the [[code]]output: 'export'[[/code]] configuration. All pages should render correctly in the static output directory.
Technical description
This PR adds a complete SEO layer to the Gitpulse site, adapting patterns from gitsky for a static-export Next.js app in a single-repo setup.
**Core SEO Infrastructure**
The new [[code ref=1]]getBaseUrl[[/code]] function resolves the site URL with fallback priority: explicit [[code]]GITPULSE_SITE_URL[[/code]] environment variable, then a GitHub Pages URL derived from [[code]]GITHUB_REPOSITORY[[/code]], finally falling back to an empty string for development previews. This mirrors the [[code]]basePath[[/code]] logic in [[code]]next.config.js[[/code]] so canonical URLs always match served routes.
Metadata builders use Next.js [[code]]Metadata[[/code]] API with OpenGraph, Twitter card, and canonical URL support. [[code]]buildHomeMetadata[[/code]] generates homepage metadata with the publication name, subtitle, and generated OG image URL. [[code]]buildStoryMetadata[[/code]] constructs per-story metadata with article type, published time, authors array, and the story-specific OG image. Titles are truncated to 70 characters with smart word-boundary breaking.
**JSON-LD Structured Data**
The [[code ref=2]]JsonLd[[/code]] component injects [[code]]application/ld+json[[/code]] script tags, escaping less-than characters to prevent XSS when user content appears in structured data. [[code]]buildWebSiteJsonLd[[/code]] outputs a WebSite schema with Organization publisher for the root layout. [[code]]buildStoryJsonLd[[/code]] outputs NewsArticle schema including headline, description, datePublished, dateModified, author (Person), articleSection (category), and wordCount derived from the story body split on whitespace.
**Static Assets**
The [[code ref=4]]robots[[/code]] route generates a robots.txt allowing all crawlers and pointing to [[code]]/sitemap.xml[[/code]] at the resolved base URL. The [[code ref=3]]sitemap[[/code]] route creates entries for the homepage (priority 1.0, daily change frequency, lastmod from newest story) and every story (priority 0.8, monthly change frequency, lastmod from mergedAt or committedAt).
**OpenGraph Images**
Root and story OG images are generated at 1200×630 using Next.js [[code]]ImageResponse[[/code]], which renders JSX to PNG at build time. The root image displays publication name and subtitle with a gold accent bar on a dark background. Story images show headline, standfirst (truncated via [[code ref=5]]clamp[[/code]]), category label, PR/commit reference, and size label. Both use a consistent serif/monospace font pairing and dark color scheme.
**Route Configuration**
All SEO routes declare [[code]]export const dynamic = 'force-static'[[/code]] — required for Next.js static export. Story pages use [[code]]generateStaticParams[[/code]] to enumerate all known story IDs at build time, matching the page route pattern.
````mermaid
graph LR
A[loadRepo / loadStories] --> B[buildHomeMetadata / buildStoryMetadata]
A --> C[buildWebSiteJsonLd / buildStoryJsonLd]
B --> D[generateMetadata API]
C --> E[JsonLd Component]
D --> F[og:* twitter:* canonical]
E --> G[JSON-LD script tag]
H[robots.ts] --> I[robots.txt]
J[sitemap.ts] --> K[sitemap.xml]
L[opengraph-image.tsx] --> M[1200x630 PNG]
````
**Files at a Glance**
- [[code]]site/src/lib/seo.ts[[/code]] — URL helpers, canonical URL construction, description truncation, home and story metadata builders
- [[code]]site/src/lib/json-ld.tsx[[/code]] — JsonLd component, WebSite and NewsArticle schema builders
- [[code]]site/src/app/robots.ts[[/code]] — robots.txt generator
- [[code]]site/src/app/sitemap.ts[[/code]] — XML sitemap with homepage and story entries
- [[code]]site/src/app/opengraph-image.tsx[[/code]] — Root OG image generator (1200×630 PNG)
- [[code]]site/src/app/stories/[id]/opengraph-image.tsx[[/code]] — Per-story OG image generator with headline, standfirst, category, size
- [[code]]site/src/app/layout.tsx[[/code]] — Mounts JsonLd with WebSite schema in head
- [[code]]site/src/app/page.tsx[[/code]] — Uses buildHomeMetadata() for generateMetadata
- [[code]]site/src/app/stories/[id]/page.tsx[[/code]] — Uses buildStoryMetadata() + embeds story JSON-LD
Categories
- New Feature (85%) — New SEO capabilities: robots.txt, XML sitemap, OpenGraph images, JSON-LD structured data, and per-page metadata — none of which existed before
- Maintenance (15%) — Centralized SEO utilities consolidated into shared lib/seo.ts, replacing scattered inline metadata in page.tsx