Summary
A share page served on a verified custom domain emits Open Graph metadata that points at cap.so. The og:url, og:image, og:video and <link rel="canonical"> values all use NEXT_PUBLIC_WEB_URL. Slack reads that mismatch and drops the card down to a bare title and description. No thumbnail appears.
Steps to reproduce
- Set a custom domain on an organization and verify it.
- Open a public share link on the custom domain. Example:
https://looms.dmdfajardo.pro/s/6g6r5spzmwf3jkn.
- Read the response with a crawler user agent.
- Post the same link in Slack.
What happens
The custom domain returns the exact same meta tags as cap.so:
<meta property="og:url" content="https://cap.so/s/6g6r5spzmwf3jkn"/>
<meta property="og:image" content="https://cap.so/api/video/preview?videoId=6g6r5spzmwf3jkn&fallback=og"/>
<meta property="og:image" content="https://cap.so/api/video/og?videoId=6g6r5spzmwf3jkn"/>
<meta property="og:video" content="https://cap.so/api/playlist?videoId=6g6r5spzmwf3jkn&videoType=mp4"/>
<link rel="canonical" href="https://cap.so/s/6g6r5spzmwf3jkn"/>
Slack renders the cap.so link as a full video card. Slack renders the custom domain link as a title and a description with no image.
The image endpoints work on the custom domain. https://looms.dmdfajardo.pro/api/video/og?videoId=... returns HTTP 200 and image/png. Only the advertised URLs are wrong.
Cause
generateMetadata runs before the custom domain lookup and never receives it.
apps/web/app/s/[videoId]/page.tsx:256 passes webUrl: buildEnv.NEXT_PUBLIC_WEB_URL into buildShareVideoMetadata.
apps/web/app/s/[videoId]/page.tsx:274, :294 and :308 hardcode the same value in the restricted and password-protected branches.
apps/web/app/s/[videoId]/page.tsx:531-558 resolves customDomain and domainVerified, but the page component uses the result. The metadata path does not.
apps/web/lib/share-video-metadata.ts builds every absolute URL from the webUrl argument, so a correct argument fixes the whole block.
The request already knows the host. The response sets verified_domain=looms.dmdfajardo.pro as a cookie, and x-nextjs-rewritten-path shows the rewrite.
Expected behavior
When a request arrives on a verified custom domain, the share page emits og:url, og:image, og:video and canonical on that domain.
Suggested fix
- Read the verified custom domain inside
generateMetadata, from the request host or from the same organization lookup the page uses.
- Pass it as
webUrl to buildShareVideoMetadata.
- Use the same value in the
PolicyDenied and VerifyVideoPasswordError branches.
- Fall back to
buildEnv.NEXT_PUBLIC_WEB_URL when the domain is absent or unverified.
Related
apps/web/slack-app-manifest.json sets "unfurl_domains": ["cap.so", "cap.link"], so the Cap Slack app never unfurls a custom domain. A custom domain link always falls back to the generic Slack crawler. That makes correct Open Graph metadata the only preview path those links have.
Issue #220 covers a different case. It reports thumbnails that point at production during local development.
Summary
A share page served on a verified custom domain emits Open Graph metadata that points at
cap.so. Theog:url,og:image,og:videoand<link rel="canonical">values all useNEXT_PUBLIC_WEB_URL. Slack reads that mismatch and drops the card down to a bare title and description. No thumbnail appears.Steps to reproduce
https://looms.dmdfajardo.pro/s/6g6r5spzmwf3jkn.What happens
The custom domain returns the exact same meta tags as
cap.so:Slack renders the
cap.solink as a full video card. Slack renders the custom domain link as a title and a description with no image.The image endpoints work on the custom domain.
https://looms.dmdfajardo.pro/api/video/og?videoId=...returns HTTP 200 andimage/png. Only the advertised URLs are wrong.Cause
generateMetadataruns before the custom domain lookup and never receives it.apps/web/app/s/[videoId]/page.tsx:256passeswebUrl: buildEnv.NEXT_PUBLIC_WEB_URLintobuildShareVideoMetadata.apps/web/app/s/[videoId]/page.tsx:274,:294and:308hardcode the same value in the restricted and password-protected branches.apps/web/app/s/[videoId]/page.tsx:531-558resolvescustomDomainanddomainVerified, but the page component uses the result. The metadata path does not.apps/web/lib/share-video-metadata.tsbuilds every absolute URL from thewebUrlargument, so a correct argument fixes the whole block.The request already knows the host. The response sets
verified_domain=looms.dmdfajardo.proas a cookie, andx-nextjs-rewritten-pathshows the rewrite.Expected behavior
When a request arrives on a verified custom domain, the share page emits
og:url,og:image,og:videoandcanonicalon that domain.Suggested fix
generateMetadata, from the request host or from the same organization lookup the page uses.webUrltobuildShareVideoMetadata.PolicyDeniedandVerifyVideoPasswordErrorbranches.buildEnv.NEXT_PUBLIC_WEB_URLwhen the domain is absent or unverified.Related
apps/web/slack-app-manifest.jsonsets"unfurl_domains": ["cap.so", "cap.link"], so the Cap Slack app never unfurls a custom domain. A custom domain link always falls back to the generic Slack crawler. That makes correct Open Graph metadata the only preview path those links have.Issue #220 covers a different case. It reports thumbnails that point at production during local development.