feat: add Open Graph and Twitter Card meta tags for public pages
- Introduce appBaseUrl config option for absolute URLs in social cards - Generate og metadata for song and playlist pages with title, description, image, and URL - Include partial layout support for rendering OG/Twitter meta tags conditionally
This commit is contained in:
parent
20bd6422a6
commit
347cf97da7
|
|
@ -55,7 +55,8 @@ const { csrfSynchronisedProtection, generateToken } = csrfSync({
|
||||||
|
|
||||||
// Attach helpers for templates.
|
// Attach helpers for templates.
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
res.locals.siteName = process.env.SITE_NAME || 'Bri-Tunes';
|
res.locals.siteName = process.env.SITE_NAME || 'Bri-Tunes';
|
||||||
|
res.locals.appBaseUrl = process.env.APP_BASE_URL || 'http://localhost:3000';
|
||||||
res.locals.user = req.session.user || null;
|
res.locals.user = req.session.user || null;
|
||||||
res.locals.flash = req.session.flash || null;
|
res.locals.flash = req.session.flash || null;
|
||||||
delete req.session.flash;
|
delete req.session.flash;
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,13 @@ router.get('/songs/:slug', (req, res, next) => {
|
||||||
const userId = req.session.user?.id || null;
|
const userId = req.session.user?.id || null;
|
||||||
social.enrichSongs([song], userId);
|
social.enrichSongs([song], userId);
|
||||||
const creator = song.uploadedBy ? users.findById(song.uploadedBy) : null;
|
const creator = song.uploadedBy ? users.findById(song.uploadedBy) : null;
|
||||||
res.render('public/song', { title: song.title, song, creator });
|
const og = {
|
||||||
|
title: `${res.locals.siteName}: ${song.title} by ${song.artist}`,
|
||||||
|
description: `Check out ${song.artist}'s hit ${song.title}, available on ${res.locals.siteName}`,
|
||||||
|
image: song.coverPath ? `${res.locals.appBaseUrl}/media/${song.coverPath}` : null,
|
||||||
|
url: `${res.locals.appBaseUrl}/songs/${song.slug}`,
|
||||||
|
};
|
||||||
|
res.render('public/song', { title: song.title, song, creator, og });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/playlists', (req, res) => {
|
router.get('/playlists', (req, res) => {
|
||||||
|
|
@ -101,7 +107,13 @@ router.get('/playlists/:slug', (req, res, next) => {
|
||||||
const tracks = playlists.getTracks(playlist.id).filter(t => canView(t.visibility, t.uploadedBy, userId));
|
const tracks = playlists.getTracks(playlist.id).filter(t => canView(t.visibility, t.uploadedBy, userId));
|
||||||
social.enrichSongs(tracks, userId);
|
social.enrichSongs(tracks, userId);
|
||||||
const creator = playlist.createdBy ? users.findById(playlist.createdBy) : null;
|
const creator = playlist.createdBy ? users.findById(playlist.createdBy) : null;
|
||||||
res.render('public/playlist', { title: playlist.title, playlist, tracks, creator });
|
const og = {
|
||||||
|
title: `${res.locals.siteName} Playlist: ${playlist.title}`,
|
||||||
|
description: playlist.description || null,
|
||||||
|
image: playlist.coverPath ? `${res.locals.appBaseUrl}/media/${playlist.coverPath}` : null,
|
||||||
|
url: `${res.locals.appBaseUrl}/playlists/${playlist.slug}`,
|
||||||
|
};
|
||||||
|
res.render('public/playlist', { title: playlist.title, playlist, tracks, creator, og });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/profiles', (req, res) => {
|
router.get('/profiles', (req, res) => {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,18 @@
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<meta name="csrf-token" content="<%= csrfToken %>">
|
<meta name="csrf-token" content="<%= csrfToken %>">
|
||||||
<title><%= typeof title !== 'undefined' ? title : siteName %></title>
|
<title><%= typeof title !== 'undefined' ? title : siteName %></title>
|
||||||
|
<% if (typeof og !== 'undefined' && og) { %>
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:site_name" content="<%= siteName %>">
|
||||||
|
<meta property="og:title" content="<%= og.title %>">
|
||||||
|
<meta property="og:url" content="<%= og.url %>">
|
||||||
|
<% if (og.description) { %><meta property="og:description" content="<%= og.description %>"><% } %>
|
||||||
|
<% if (og.image) { %><meta property="og:image" content="<%= og.image %>"><% } %>
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="twitter:title" content="<%= og.title %>">
|
||||||
|
<% if (og.description) { %><meta name="twitter:description" content="<%= og.description %>"><% } %>
|
||||||
|
<% if (og.image) { %><meta name="twitter:image" content="<%= og.image %>"><% } %>
|
||||||
|
<% } %>
|
||||||
<link rel="preload" href="/static/vendor/fonts/Orbitron.woff2" as="font" type="font/woff2" crossorigin>
|
<link rel="preload" href="/static/vendor/fonts/Orbitron.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
<link rel="preload" href="/static/vendor/fonts/Inter.woff2" as="font" type="font/woff2" crossorigin>
|
<link rel="preload" href="/static/vendor/fonts/Inter.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
<link rel="icon" href="/static/favicon.ico" sizes="any">
|
<link rel="icon" href="/static/favicon.ico" sizes="any">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue