Skip to content

Astro Integration

Generate OG images for Astro websites.

Setup

1. Environment Variable

bash
# .env
OG_IMAGE_API_KEY=og_your_api_key_here

2. API Endpoint

javascript
// src/pages/api/og.png.js
export async function GET({ request }) {
  const url = new URL(request.url);
  const title = url.searchParams.get('title') || 'My Astro Site';
  const subtitle = url.searchParams.get('subtitle') || '';
  
  const response = await fetch('https://ogimageapi.io/api/generate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': import.meta.env.OG_IMAGE_API_KEY
    },
    body: JSON.stringify({
      title,
      subtitle,
      template: 'default',
      theme: 'dark'
    })
  });
  
  const imageBuffer = await response.arrayBuffer();
  
  return new Response(imageBuffer, {
    headers: {
      'Content-Type': 'image/png',
      'Cache-Control': 'public, max-age=86400'
    }
  });
}

SEO Component

astro
---
// src/components/SEO.astro
interface Props {
  title: string;
  description?: string;
  image?: string;
  type?: string;
}

const { 
  title, 
  description = '', 
  image = null,
  type = 'website'
} = Astro.props;

const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const ogImage = image || `/api/og.png?title=${encodeURIComponent(title)}&subtitle=${encodeURIComponent(description)}`;
---

<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonicalURL} />

<!-- Open Graph -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonicalURL} />
<meta property="og:image" content={ogImage} />
<meta property="og:type" content={type} />

<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={ogImage} />

Usage in Pages

astro
---
// src/pages/blog/[slug].astro
import SEO from '../../components/SEO.astro';
import { getPost } from '../../lib/posts';

export async function getStaticPaths() {
  const posts = await getAllPosts();
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post }
  }));
}

const { post } = Astro.props;
---

<html>
  <head>
    <SEO
      title={post.title}
      description={post.excerpt}
      type="article"
    />
  </head>
  <body>
    <article>
      <h1>{post.title}</h1>
      <Fragment set:html={post.content} />
    </article>
  </body>
</html>

Build-Time Generation

Generate all OG images during build:

Integration

javascript
// integrations/og-images.js
import fs from 'fs';
import path from 'path';

export default function ogImages() {
  return {
    name: 'og-images',
    hooks: {
      'astro:build:done': async ({ pages }) => {
        console.log('Generating OG images...');
        
        const outputDir = './dist/og';
        fs.mkdirSync(outputDir, { recursive: true });
        
        for (const page of pages) {
          if (page.pathname.startsWith('blog/')) {
            const slug = page.pathname.replace('blog/', '').replace('/', '');
            const post = await getPost(slug);
            
            const response = await fetch('https://ogimageapi.io/api/generate', {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                'X-API-Key': process.env.OG_IMAGE_API_KEY
              },
              body: JSON.stringify({
                title: post.title,
                subtitle: post.excerpt,
                author_name: post.author,
                template: 'blog',
                theme: 'dark'
              })
            });
            
            const buffer = await response.arrayBuffer();
            fs.writeFileSync(
              path.join(outputDir, `${slug}.png`),
              Buffer.from(buffer)
            );
            
            console.log(`✓ ${slug}.png`);
          }
        }
      }
    }
  };
}

Astro Config

javascript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import ogImages from './integrations/og-images.js';

export default defineConfig({
  integrations: [ogImages()]
});

Content Collections

astro
---
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content';
import SEO from '../../components/SEO.astro';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post }
  }));
}

const { post } = Astro.props;
const { Content } = await post.render();

const ogImage = `/og/${post.slug}.png`;
---

<html>
  <head>
    <SEO
      title={post.data.title}
      description={post.data.description}
      image={ogImage}
      type="article"
    />
  </head>
  <body>
    <article>
      <h1>{post.data.title}</h1>
      <Content />
    </article>
  </body>
</html>

Script for Batch Generation

javascript
// scripts/generate-og.mjs
import { getCollection } from 'astro:content';
import fs from 'fs';
import path from 'path';

async function generateOGImages() {
  const posts = await getCollection('blog');
  const outputDir = './public/og';
  
  fs.mkdirSync(outputDir, { recursive: true });
  
  for (const post of posts) {
    const response = await fetch('https://ogimageapi.io/api/generate', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': process.env.OG_IMAGE_API_KEY
      },
      body: JSON.stringify({
        title: post.data.title,
        subtitle: post.data.description,
        author_name: post.data.author,
        template: 'blog',
        theme: 'dark'
      })
    });
    
    const buffer = await response.arrayBuffer();
    fs.writeFileSync(
      path.join(outputDir, `${post.slug}.png`),
      Buffer.from(buffer)
    );
    
    console.log(`Generated: ${post.slug}.png`);
  }
}

generateOGImages();

SSR Mode

For server-rendered Astro:

javascript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  output: 'server',
  adapter: vercel()
});
astro
---
// src/pages/og/[slug].png.js
export async function GET({ params }) {
  const post = await getPost(params.slug);
  
  const response = await fetch('https://ogimageapi.io/api/generate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': import.meta.env.OG_IMAGE_API_KEY
    },
    body: JSON.stringify({
      title: post.title,
      subtitle: post.excerpt,
      template: 'blog',
      theme: 'dark'
    })
  });
  
  return new Response(await response.arrayBuffer(), {
    headers: {
      'Content-Type': 'image/png',
      'Cache-Control': 'public, max-age=86400'
    }
  });
}
---

Best Practices

  1. Generate at build time for static sites
  2. Use integrations for automatic generation
  3. Cache responses when using SSR
  4. Use content collections for typed content
  5. Test locally before deploying

Generate stunning Open Graph images programmatically.