├── .gitignore ├── tsconfig.json ├── package.json ├── src └── index.ts └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "module": "ES2020", 9 | "moduleResolution": "node", 10 | "outDir": "./dist", 11 | "skipLibCheck": true, 12 | "strict": true, 13 | "target": "ES2021" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-og-image", 3 | "version": "1.0.5", 4 | "description": "An astro integration to generate static Open Graph images, at build time", 5 | "type": "module", 6 | "types": "./dist/index.d.ts", 7 | "author": "Tomáš Kebrle (https://tomaskebrle.cz)", 8 | "homepage": "https://github.com/Kendy205/astro-og-image", 9 | "bugs": "https://github.com/Kendy205/astro-og-image/issues", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/Kendy205/astro-og-image.git", 13 | "directory": "/" 14 | }, 15 | "license": "MIT", 16 | "exports": { 17 | ".": "./dist/index.js" 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "build": "tsc" 24 | }, 25 | "keywords": [ 26 | "astro-component", 27 | "open-graph", 28 | "seo" 29 | ], 30 | "devDependencies": { 31 | "astro": "^1.0.0-beta.57", 32 | "typescript": "^4.7.4" 33 | }, 34 | "dependencies": { 35 | "puppeteer": "^15.1.1" 36 | }, 37 | "engines": { 38 | "node": ">=14.0.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import puppeteer from "puppeteer"; 3 | import { fileURLToPath } from "node:url"; 4 | import type { AstroIntegration, RouteData } from "astro"; 5 | 6 | export default function astroOGImage({ 7 | config, 8 | }: { 9 | config: { path: string }; 10 | }): AstroIntegration { 11 | return { 12 | name: "astro-og-image", 13 | hooks: { 14 | "astro:build:done": async ({ dir, routes }) => { 15 | let path = config.path; 16 | // Filters all the routes that need OG image 17 | let filteredRoutes = routes.filter((route: RouteData) => 18 | route?.pathname?.includes(path) 19 | ); 20 | 21 | // Creates a directory for the images if it doesn't exist already 22 | let directory = fileURLToPath(new URL(`./assets/${path}`, dir)); 23 | if (!fs.existsSync(directory)) { 24 | fs.mkdirSync(directory); 25 | } 26 | 27 | const browser = await puppeteer.launch(); 28 | for (const route of filteredRoutes) { 29 | // Gets the title 30 | const data = fs.readFileSync( 31 | route?.distURL?.pathname as any, 32 | "utf-8" 33 | ) as any; 34 | let title = await data.match(/]*>([^<]+)<\/title>/)[1]; 35 | 36 | // Get the html 37 | const html = fs 38 | .readFileSync("og-image.html", "utf-8") 39 | .toString() 40 | .replace("@title", title); 41 | 42 | const page = await browser.newPage(); 43 | await page.setContent(html); 44 | await page.waitForNetworkIdle(); 45 | await page.setViewport({ 46 | width: 1200, 47 | height: 630, 48 | }); 49 | 50 | await page.screenshot({ 51 | path: fileURLToPath(new URL(`./assets${route.pathname}.png`, dir)), 52 | encoding: "binary", 53 | }); 54 | } 55 | await browser.close(); 56 | }, 57 | }, 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :rocket: Astro Open Graph Image 2 | An astro integration to generate static Open Graph images, at build time 3 | 4 | # Setup 5 | ## Prerequisites 6 | 7 | You will need an astro site with a blog. And then you need to install [astro-og-image package](https://www.npmjs.com/package/astro-og-image). 8 | 9 | ```bash 10 | npm i astro-og-image 11 | pnpm add astro-og-image 12 | yarn add astro-og-image 13 | ``` 14 | 15 | Lastly you need to add the `--experimental-integrations` flag to your scripts in `package.json` 16 | 17 | ```json 18 | "scripts": { 19 | "dev": "astro dev --host --experimental-integrations", 20 | "start": "astro dev --experimental-integrations", 21 | "build": "astro build --experimental-integrations", 22 | "preview": "astro preview --experimental-integrations" 23 | } 24 | ``` 25 | 26 | ## Config 27 | Now you will need to import the integration in astro config 28 | 29 | ```typescript 30 | import {defineConfig} from "astro/config"; 31 | import astroOGImage from "astro-og-image"; 32 | export default defineConfig({ 33 | integrations: [ 34 | astroOGImage({ 35 | config: { 36 | path: "/posts", // change this value to the folder where your posts are 37 | // NOTE: index.md file will not get proccesed, so please avoid it 38 | }, 39 | }), 40 | ], 41 | }); 42 | ``` 43 | 44 | ## Creating template for the image 45 | 46 | As I stated before the image will be created by screenshotting an HTML page. The integration will load the HTML from `og-image.html` file, so create one **in the root directory** and put your template inside. 47 | 48 | ```html 49 | 50 | 51 | 52 | 53 | 54 | 55 | Document 56 | 57 | 58 |

@title

59 | 60 | 61 | ``` 62 | 63 | 64 | Note that the `@title` will then be replaced by the title of your post. 65 | 66 | ## Adding the Open Graph image property to your BaseHead 67 | 68 | First of all you will need to add the slug property to your markdown files something like this 69 | 70 | ```md 71 | --- 72 | title: My first 73 | publishDate: 28 Jun 2022 74 | description: The very first post on my new blog 75 | slug: my-post // <-- NOTE: slug must be the same as file name 76 | --- 77 | ``` 78 | 79 | 80 | Then in your blogpost layout modify the BaseHead component to accept the slug as a parameter 81 | 82 | ```astro 83 | --- 84 | const { title, description, publishDate, slug } = content; // Destructure it here 85 | --- 86 | 87 | 88 | 89 | 90 | ``` 91 | 92 | And modify the BaseHead component meta tags. 93 | _I recomennd that you use a fallback image for all the non posts pages, and in case something goes wrong._ 94 | 95 | ```astro 96 | --- 97 | export interface Props { 98 | title: string; 99 | description: string; 100 | slug: string; 101 | } 102 | const { title, description, slug, publishDate } = Astro.props; 103 | --- 104 | 105 | 106 | 107 | ``` 108 | 109 | 110 | ## Final steps 111 | 112 | Great now you should be done!🎉 Deploy your site and test it out. Great site for testing this out is [Open Graph previewer](https://www.opengraph.xyz/). 113 | 114 | 115 | --------------------------------------------------------------------------------