feat: add shareable meme permalinks

This commit is contained in:
2026-07-24 15:37:22 -03:00
parent 4d317ba1d3
commit 0bf215555a
7 changed files with 334 additions and 33 deletions
+38 -20
View File
@@ -13,36 +13,54 @@ export function publicBaseUrl(req) {
return cleanBase(`${proto}://${host || 'localhost:8080'}`);
}
export function renderIndex({ template, baseUrl, nonce, approvedCount }) {
const canonical = `${baseUrl}/`;
const image = `${baseUrl}/assets/yoonect-logo.png`;
const jsonLd = {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'WebSite',
'@id': `${canonical}#website`,
name: SITE_NAME,
export function renderIndex({ template, baseUrl, nonce, approvedCount, meme = null }) {
const home = `${baseUrl}/`;
const canonical = meme ? `${baseUrl}/meme/${meme.id}` : home;
const image = meme ? `${baseUrl}/media/${meme.id}` : `${baseUrl}/assets/yoonect-logo.png`;
const title = meme ? `Meme ${shortId(meme.id)} | ${SITE_NAME}` : SITE_NAME;
const description = meme
? `View and share meme ${shortId(meme.id)} on ${SITE_NAME}.`
: SITE_DESCRIPTION;
const pageMetadata = meme
? {
'@type': 'ImageObject',
'@id': `${canonical}#meme`,
name: `Meme ${shortId(meme.id)}`,
contentUrl: image,
url: canonical,
description: SITE_DESCRIPTION,
inLanguage: 'en'
},
{
uploadDate: meme.createdAt,
width: meme.width,
height: meme.height,
isPartOf: { '@id': `${home}#website` }
}
: {
'@type': 'CollectionPage',
'@id': `${canonical}#collection`,
name: SITE_NAME,
url: canonical,
description: SITE_DESCRIPTION,
isPartOf: { '@id': `${canonical}#website` },
isPartOf: { '@id': `${home}#website` },
about: ['memes', 'internet culture', 'image gallery', 'community moderation'],
numberOfItems: approvedCount
};
const jsonLd = {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'WebSite',
'@id': `${home}#website`,
name: SITE_NAME,
url: home,
description: SITE_DESCRIPTION,
inLanguage: 'en'
},
pageMetadata,
{
'@type': 'SoftwareApplication',
name: SITE_NAME,
applicationCategory: 'MultimediaApplication',
operatingSystem: 'Web',
url: canonical,
url: home,
codeRepository: REPO_URL,
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' }
}
@@ -50,8 +68,8 @@ export function renderIndex({ template, baseUrl, nonce, approvedCount }) {
};
return template
.replaceAll('__SEO_TITLE__', escapeHtml(SITE_NAME))
.replaceAll('__SEO_DESCRIPTION__', escapeHtml(SITE_DESCRIPTION))
.replaceAll('__SEO_TITLE__', escapeHtml(title))
.replaceAll('__SEO_DESCRIPTION__', escapeHtml(description))
.replaceAll('__SEO_CANONICAL__', canonical)
.replaceAll('__SEO_IMAGE__', image)
.replaceAll('__SEO_JSON_LD__', escapeJsonScript(JSON.stringify(jsonLd)))
@@ -125,7 +143,7 @@ export function feedJson(baseUrl, memes) {
description: SITE_DESCRIPTION,
items: memes.slice(0, 50).map((meme) => ({
id: meme.id,
url: `${baseUrl}/media/${meme.id}`,
url: `${baseUrl}/meme/${meme.id}`,
image: `${baseUrl}/media/${meme.id}`,
title: `Meme ${shortId(meme.id)}`,
content_text: `Approved meme with MEME_CONSENSUS_SCORE ${meme.moderationScore}/100.`,
@@ -145,7 +163,7 @@ export function sitemapXml(baseUrl, memes) {
].join('\n'),
...memes.slice(0, 1000).map((meme) => [
' <url>',
` <loc>${xmlEscape(`${baseUrl}/media/${meme.id}`)}</loc>`,
` <loc>${xmlEscape(`${baseUrl}/meme/${meme.id}`)}</loc>`,
` <lastmod>${xmlEscape(meme.createdAt)}</lastmod>`,
' </url>'
].join('\n'))