-
-
Notifications
You must be signed in to change notification settings - Fork 48
feat: Decode /_og/d/** route using full catch-all segment (avoid losing props when %2F is decoded to /) #522
Description
🆒 Your use case
Summary
nuxt-og-image builds dynamic OG image URLs under /_og/d/<encoded-options>.<ext>, where <encoded-options> is a comma-separated, encodeURIComponent-encoded payload (including component props such as coverSrc for site-relative paths like /img/foo/bar.jpg).
Problem: Some environments and tools (notably Vercel’s deployment “Open Graph” preview, and other crawlers/debuggers) normalize or display the same logical URL with literal / characters where the wire format used %2F. After that normalization, the HTTP path is split into multiple segments between /d/ and the final .png.
The module’s server handler currently derives the encoded options string with logic equivalent to:
- take
pathnamefrom the request path.split('/').pop()to get the “filename” segment- strip the extension and
decodeOgImageParams(...)
When unencoded / characters appear inside what should be a single logical segment (because %2F was decoded), .pop() returns only the last fragment. The decoded options then omit most props (component, headline, subheadline, coverSrc, etc.), while fragments that happen to live in the last segment (e.g. the p / _path base64 chunk) may still parse. The generated image becomes a partial/broken card (e.g. badge + author + site name only), even though the canonical og:image URL in HTML (with %2F) works.
This creates inconsistent previews between:
- fetching the exact
og:imagestring from HTML, and - tools that rewrite the URL with decoded slashes before requesting the image.
Reproduction (conceptual)
- Define an OG image with a prop whose encoded form contains
%2F(typical for a site-relative file path in a prop such ascoverSrc, e.g./img/blog/post/cover.jpg). - Confirm the page’s
og:imagemeta URL works when requested as emitted (percent-encoded). - Request the same logical URL after replacing
%2Fwith/in the path (simulating a UI or fetcher that decodes path segments). - Observe that the image response may correspond to truncated/partial decode options (missing headline, wrong template props, missing hero image), or otherwise diverges from (2).
🆕 The solution you'd like
Suggested direction (optimal fix)
Preferred: For the Nitro route /_og/d/**, resolve the encoded options from the full catch-all match (the entire remainder of the path after /_og/d/), including internal / characters, up to the final .<extension>, instead of using only pathname.split('/').pop().
Concretely, the handler should reconstruct one string:
encodedSegment = <everything after /_og/d/ without the leading slash, with path segments joined, until the file extension>
or equivalently read the raw matched ** segment from the router and strip .png (etc.) from the end.
That way:
- Requests with
%2F(single path segment in the URI) continue to work as today. - Requests where intermediaries have turned
%2Finto/still map to the same decoded options as long as the server receives the full path string (which is the usual case for/_og/d/**).
Alternative / additional hardening: If relying on raw path parsing remains fragile across hosts, the module could document or support passing path-like props only in non-slash encodings (e.g. optional base64url field) — but that pushes complexity to users; fixing segment extraction is the more general solution.
🔍 Alternatives you've considered
Workaround (today)
Consumers can avoid / inside individual prop values (e.g. encode site paths as base64url in coverSrc and decode in the template). That works but is non-obvious and duplicates logic in every project that uses path-like OG props.
Why this is worth fixing upstream
- Social / SEO tooling should be able to fetch
og:imageURLs that are equivalent under normal URI normalization without changing semantics. - Developer experience: Path-shaped props are natural for hero/cover images; users should not need custom encoding solely because some platforms decode
%2F.
ℹ️ Additional info
No response