fix(share): OG/Twitter-card metadata for gallery share URLs (#333)
WhatsApp / Slack / Facebook / Twitter previews showed nothing useful for shared gallery links — the SPA's stub index.html has no OG tags and the meta-injection in DynamicFavicon happens at runtime, which crawlers never see (they don't execute JS). Add a backend OG handler at /og/gallery/:slug that returns minimal HTML with proper og:* and twitter:* meta sourced from the event row + branding settings (event name, formatted date, welcome_message excerpt as description, configured logo as the preview image, FRONTEND_URL-based canonical). Honours slug redirects so renamed galleries still get rich previews. Wire crawler detection in both nginx configs (production and dev) — UA match against the standard list (facebookexternalhit, WhatsApp, Slackbot, Twitterbot, Discordbot, LinkedInBot, etc.) triggers an internal rewrite to /og/gallery/:slug, while humans fall through to the SPA via try_files. The OG endpoint is also wired into the native-install SPA fallback in server.js for setups that bypass nginx. The OG image is intentionally the brand logo, not a gallery photo — crawlers fetch it without auth, and password-protected gallery photos must not leak via share previews.
This commit is contained in:
+17
-1
@@ -470,6 +470,13 @@ if (process.env.NODE_ENV === 'development') {
|
||||
});
|
||||
}
|
||||
|
||||
// OG/Twitter-card preview endpoint for gallery share URLs. Crawlers (WhatsApp,
|
||||
// Slack, Facebook, etc.) don't execute JS, so the SPA's client-side meta tags
|
||||
// never reach them. nginx routes UA-detected crawlers from /gallery/:slug to
|
||||
// here; humans still get the SPA via try_files.
|
||||
const { isSocialCrawler, handleGalleryOgRequest } = require('./src/services/galleryOgService');
|
||||
app.get('/og/gallery/:slug', handleGalleryOgRequest);
|
||||
|
||||
// robots.txt endpoint (dynamic, served from DB settings)
|
||||
const { generateRobotsTxt } = require('./src/services/robotsTxtService');
|
||||
app.get('/robots.txt', async (req, res) => {
|
||||
@@ -576,7 +583,16 @@ try {
|
||||
res.sendFile(indexPath);
|
||||
});
|
||||
|
||||
// SPA fallback for admin + gallery routes
|
||||
// SPA fallback for admin + gallery routes. For gallery URLs we intercept
|
||||
// social-crawler User-Agents and serve OG/Twitter-card metadata so link
|
||||
// previews show the event name + branding instead of the SPA stub.
|
||||
app.get('/gallery/:slug/:token?', (req, res, next) => {
|
||||
if (isSocialCrawler(req.get('user-agent'))) {
|
||||
return handleGalleryOgRequest(req, res);
|
||||
}
|
||||
return next();
|
||||
}, (req, res) => res.sendFile(indexPath));
|
||||
|
||||
app.get(['/admin', '/admin/*', '/gallery/*'], (req, res) => {
|
||||
res.sendFile(indexPath);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user