Add Open Graph and Twitter Card meta-tags for link previews
All checks were successful
CI / backend-test (push) Successful in 1m9s
CI / frontend-test (push) Successful in 23s
CI / frontend-e2e (push) Successful in 1m12s
CI / build-and-publish (push) Has been skipped

Replace PathResourceResolver SPA fallback with SpaController that
injects OG/Twitter meta-tags into cached index.html template.
Event pages get event-specific tags (title, date, location),
all other pages get generic fete branding. Includes og-image.png
brand asset and forward-headers-strategy for proxy support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 20:25:39 +01:00
parent fa34223c10
commit 751201617d
16 changed files with 1270 additions and 28 deletions

View File

@@ -0,0 +1,98 @@
# Contract: HTML Meta-Tags
**Feature**: 012-link-preview | **Date**: 2026-03-09
## Overview
This feature does not add new REST API endpoints. The contract is the HTML meta-tag structure injected into the server-rendered `index.html`.
## Meta-Tag Contract: Event Pages
For requests to `/events/{eventToken}` where the event exists:
```html
<!-- Open Graph -->
<meta property="og:title" content="{event title, max 70 chars}">
<meta property="og:description" content="{formatted description, max 200 chars}">
<meta property="og:url" content="{absolute canonical URL}">
<meta property="og:type" content="website">
<meta property="og:site_name" content="fete">
<meta property="og:image" content="{absolute URL}/og-image.png">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{event title, max 70 chars}">
<meta name="twitter:description" content="{formatted description, max 200 chars}">
```
### Description Format
Full event data:
```
📅 Saturday, March 15, 2026 at 7:00 PM · 📍 Berlin — First 200 chars of description...
```
No location:
```
📅 Saturday, March 15, 2026 at 7:00 PM — First 200 chars of description...
```
No description:
```
📅 Saturday, March 15, 2026 at 7:00 PM · 📍 Berlin
```
No location, no description:
```
📅 Saturday, March 15, 2026 at 7:00 PM
```
### Title Truncation
- Titles ≤ 70 characters: used as-is.
- Titles > 70 characters: truncated to 67 characters + "..."
### HTML Escaping
All meta-tag `content` values MUST be HTML-escaped:
- `"``&quot;`
- `&``&amp;`
- `<``&lt;`
- `>``&gt;`
## Meta-Tag Contract: Non-Event Pages
For requests to `/`, `/create`, or any other non-event, non-API, non-static route:
```html
<!-- Open Graph -->
<meta property="og:title" content="fete">
<meta property="og:description" content="Privacy-focused event planning. Create and share events without accounts.">
<meta property="og:url" content="{absolute canonical URL}">
<meta property="og:type" content="website">
<meta property="og:site_name" content="fete">
<meta property="og:image" content="{absolute URL}/og-image.png">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="fete">
<meta name="twitter:description" content="Privacy-focused event planning. Create and share events without accounts.">
```
## Meta-Tag Contract: Event Not Found
For requests to `/events/{eventToken}` where the event does NOT exist, fall back to generic meta-tags (same as non-event pages). The Vue SPA will handle the 404 UI client-side.
## Injection Mechanism
The `index.html` template contains a placeholder:
```html
<head>
...
<!-- OG_META_TAGS -->
...
</head>
```
The server replaces `<!-- OG_META_TAGS -->` with the generated meta-tag block before sending the response.