feat: generate og:image when there isn't one
This commit is contained in:
parent
65032ab2fc
commit
9e7318fb63
2 changed files with 73 additions and 4 deletions
|
@ -3,6 +3,10 @@ import Layout from '../../layouts/Layout.astro';
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import Comments from "../../components/Comments.astro";
|
import Comments from "../../components/Comments.astro";
|
||||||
import {getImage} from "astro:assets";
|
import {getImage} from "astro:assets";
|
||||||
|
import { unified } from "unified";
|
||||||
|
import { select } from "unist-util-select";
|
||||||
|
import remarkMdx from "remark-mdx";
|
||||||
|
import remarkParse from "remark-parse";
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const blogEntries = await getCollection('posts');
|
const blogEntries = await getCollection('posts');
|
||||||
|
@ -14,16 +18,19 @@ export async function getStaticPaths() {
|
||||||
const { entry } = Astro.props;
|
const { entry } = Astro.props;
|
||||||
const { Content } = await entry.render();
|
const { Content } = await entry.render();
|
||||||
|
|
||||||
|
|
||||||
const slug = Astro.params.slug;
|
const slug = Astro.params.slug;
|
||||||
|
|
||||||
|
// get featured image and use it as og:image
|
||||||
|
// use the custom cover image if it exists, otherwise use the featured image file in the same directory
|
||||||
const featuredImages = import.meta.glob(`/src/content/posts/*/featured.*`,{import:'default',eager:true});
|
const featuredImages = import.meta.glob(`/src/content/posts/*/featured.*`,{import:'default',eager:true});
|
||||||
|
const customFeaturedImage = entry.data.cover?.src
|
||||||
const matchedImage = Object.keys(featuredImages).find(path => path.includes(slug));
|
const matchedImage = Object.keys(featuredImages).find(path => path.includes(slug));
|
||||||
let matchedImage_src;
|
let matchedImage_src;
|
||||||
if (matchedImage) {
|
if (matchedImage && !customFeaturedImage) {
|
||||||
matchedImage_src = await getImage({src: featuredImages[matchedImage], format: 'webp'}) || null;
|
matchedImage_src = await getImage({src: featuredImages[matchedImage], format: 'webp'}) || null;
|
||||||
}
|
}
|
||||||
const cover = entry.data.cover || matchedImage_src?.src || `/post/${slug}/og.webp`;
|
|
||||||
//|| `/posts/${slug}/featured.webp` || `/posts/${slug}/featured.png` || `/posts/${slug}/featured.jpg`
|
const cover = customFeaturedImage || matchedImage_src?.src || `/post/${slug}/featured.png` || '';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
|
62
src/pages/post/[slug]/featured.png.js
Normal file
62
src/pages/post/[slug]/featured.png.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import {getImage} from "astro:assets";
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const blogEntries = await getCollection('posts');
|
||||||
|
return blogEntries.map(post => ({
|
||||||
|
params: { slug: post.slug }, props: { post },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the post has a external featured.* image files
|
||||||
|
async function getExternalImage(post) {
|
||||||
|
const featuredImages = import.meta.glob(`/src/content/posts/*/featured.*`, {import: 'default', eager: true});
|
||||||
|
const matchedImage = Object.keys(featuredImages).find(path => path.includes(post.slug));
|
||||||
|
let matchedImage_src;
|
||||||
|
if (matchedImage) {
|
||||||
|
matchedImage_src = await getImage({src: featuredImages[matchedImage], format: 'webp'}) || null;
|
||||||
|
}
|
||||||
|
return matchedImage_src;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function dynamically generates og:images for posts that don't have a featured image
|
||||||
|
export async function GET({ props }) {
|
||||||
|
const {post} = props;
|
||||||
|
try {
|
||||||
|
// Check if a custom cover image already exists
|
||||||
|
if (post.data.cover && await getExternalImage(post)) {
|
||||||
|
// Redirect to the existing image
|
||||||
|
return new Response(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate an image with post title and description
|
||||||
|
const width = 1280;
|
||||||
|
const height = 720;
|
||||||
|
|
||||||
|
// Create a simple image with text
|
||||||
|
const svg = `
|
||||||
|
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="${width}" height="${height}" fill="#2e3440"/>
|
||||||
|
<text x="50%" y="40%" font-family="JetBrains Mono, monospace" font-size="50" text-anchor="middle" fill="#eceff4">${post.data.title}</text>
|
||||||
|
<text x="50%" y="60%" font-family="JetBrains Mono, monospace" font-size="30" text-anchor="middle" fill="#81a1c1">${post.data.description}</text>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Convert SVG to WebP
|
||||||
|
const buffer = await sharp(Buffer.from(svg))
|
||||||
|
.toFormat('webp')
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
// Return the image
|
||||||
|
return new Response(buffer, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'image/png',
|
||||||
|
'Cache-Control': 'public, max-age=31536000'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating image:', error);
|
||||||
|
return new Response('Error generating image', { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue