Je voulais développer une fonction sans serveur pour générer des images Open Graph dynamiques depuis si longtemps, et je l’ai finalement fait la semaine dernière.
Voici à quoi ressemblent mes images OG maintenant :
Exigences et limites
Tout d’abord, examinons les exigences. Je voulais utiliser SVG comme modèle. Malheureusement, SVG n’est pas un format pris en charge pour les images OG. Au lieu de cela, SVG doit être converti dans un autre format d’image, comme PNG ou JPG. Cela signifie que j’aurai besoin d’une bibliothèque pour cela.
La deuxième exigence concerne la longueur du titre. Depuis que j’utilise cette fonction sur tout le site, la taille du titre varie beaucoup, de très courte à très longue. Ainsi, si le titre est trop long, il doit être affiché sur plusieurs lignes. Cela signifie que je devrai faire quelques calculs pour la longueur du titre.
La partie facile
Créer un modèle SVG dans Figma était la partie la plus facile. J’ai décidé d’utiliser le format suggéré de 1200⨉630 pixels.
Une fois que j’ai eu le code SVG, j’ai créé un nouveau fichier dans mon netlify/functions
dossier appelé og-img.js
.
var base64 = require('base-64')
const utf8 = require('utf8')exports.handler = async function (event) {
let text = 'Home of fearless web developer Silvestar Bistrović'
if(event.queryStringParameters && event.queryStringParameters.text) {
text = event.queryStringParameters.text
}
const svg = `<svg width="1200" height="630" viewBox="0 0 1200 630" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="1200" height="630" rx="20" fill="white"/>
<rect x="56" y="56" width="1088" height="518" rx="15" stroke="#FF3366" stroke-width="14"/>
<style>
h1{
font-size:60px;
line-height:1.2;
font-weight:300;
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';
margin:0
}
</style>
<foreignObject x="260" y="279" height="247" width="816">
<h1 xmlns="http://www.w3.org/1999/xhtml">${utf8.encode(text)}</h1>
</foreignObject>
</svg>`
try {
return {
statusCode: 200,
body: base64.encode(svg),
headers: {
"content-type": "image/svg+xml"
},
isBase64Encoded: true,
}
} catch (error) {
console.log(error)
return {
statusCode: 200,
body: 'Error!',
headers: {
"content-type": "text"
}
}
}
}
Pour passer des titres dynamiquement, j’utilise le text
Paramètre d’URL. Sinon text
est trouvé, le texte par défaut sera utilisé à la place.
Pour afficher le titre, j’ai d’abord utilisé le <foreignObject>
élément.
Enfin, comme je viens de Croatie, j’ai dû encoder des caractères spéciaux et utiliser la bibliothèque utf8.
La partie délicate
Si les images OG prennent en charge SVG, le travail se ferait bien et facilement. Mais comme le format SVG n’est pas pris en charge, je devais trouver comment convertir SVG en PNG. La plupart des tutoriels utilisent Puppeteer et sa fonction de capture d’écran pour prendre la capture d’écran des images SVG générées. Je ne pouvais pas le faire fonctionner, même en essayant d’appliquer le correctif de bogue de Zach Leatherman. J’ai donc pensé que j’utiliserais la bibliothèque pour convertir SVG en PNG.
Pour convertir des images, j’ai décidé d’utiliser la bibliothèque Sharp. Mais Sharp ne fonctionne pas avec <foreignObject>
élément, j’ai donc dû trouver une solution de contournement.
Je savais que je devais utiliser le <text>
élément, que je voulais éviter car le <text>
ne peut pas envelopper le texte, j’ai donc dû faire quelques calculs pour diviser le titre en plusieurs lignes.
Voici ce que j’ai trouvé.
let textArray = []
let line = ''
const offset = 30
let text = 'Home of fearless web developer Silvestar Bistrović'if(event.queryStringParameters && event.queryStringParameters.text) {
text = event.queryStringParameters.text
}
let words = text.split(' ')
for (let i = 0; i < words.length; i++) {
if(line.length + words[i].length < offset) {
line.length += words[i].length;
line += ` ${words[i]}`
} else {
textArray.push(line)
line.length = words[i].length
line = words[i]
}
if (i === words.length - 1) {
textArray.push(line)
}
}
const yPosition = 320
const yOffset = 65
let tspan = ''
for (let i = 0; i < textArray.length; i++) {
if(i < 4) {
tspan += `<tspan x="260" y="${yPosition + (yOffset * i)}">${utf8.encode(textArray[i].trim())}</tspan>`
}
}
Tout d’abord, j’ai créé un tableau de tous les mots. Ensuite, je parcours le tableau pour vérifier si la ligne avec le mot suivant concaténé serait plus longue que ma limite de ligne, 30. Enfin, j’ajoute le <tspan>
élément pour chaque ligne et en appliquant quelques calculs pour que chaque ligne soit superposée.
La conversion de SVG en PNG était la partie la plus facile :
const img = await sharp(Buffer.from(svg))
.png()
.toBuffer()
Il ne reste plus qu’à sortir l’image encodée en base64 :
return {
statusCode: 200,
body: img.toString('base64'),
headers: {
"content-type": "image/png"
},
isBase64Encoded: true
}
Le code complet peut être vu ici : https://github.com/maliMirkec/personal-website/blob/master/netlify/functions/og-png.js.
Maintenant, je pouvais utiliser cette fonction, j’ai donc ajouté les balises OG à mon head
élément.
<meta property="og:image" content="/og/og.png?text={{ title | url_encode }}">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
Notez que mon Eleventy title
la variable est encodée avec le Liquid url_encode
filtre.
Depuis que j’utilise les fonctions Netlify et que je n’aime pas le /.netlify/functions/og-png
URL, j’ai configuré des redirections pour rendre l’URL plus jolie. J’ai ajouté la règle suivante à mon _redirects
déposer:
/og/og.png* /.netlify/functions/og-png:splat 200!
Conclusion
Je gère mon site depuis 2015 en utilisant quelques modèles d’image OG. Mais maintenant, toutes mes images Open Graph utilisent le même modèle et se ressemblent. Heck, toutes mes pages ont maintenant des images OG, ce qui n’était pas le cas auparavant.
Au fait, je sais que la police est très basique, mais j’ai choisi de l’ignorer pour l’instant.
La meilleure partie est que je n’ai pas besoin d’un logiciel comme Figma pour créer une image OG et l’héberger dans Cloudinary. Maintenant, mon site le génère pour moi gratuitement.