@altano/astro-opengraph v0.1.0-alpha.4
ā ļø EXPERIMENTAL: This package uses experimental Astro APIs. Tread carefuly. More info below.
astro-opengraph
Your Open Graph toolkit š§° for Astro, including everything you need to:
- Create Open Graph png image from .astro components.
- Manage your Open Graph meta tags (e.g.
<og:image>
,<og:image:title>
, etc). - Show you a page's Open Graph image and meta tags in
astro dev
mode with an Astro dev toolbar app. - Fit text to fill a space in your Open Graph image.
- Configure everything above with an Astro integration.
Unlike existing Astro integrations for Open Graph images, this one:
- Doesn't require browsers or screenshotting (e.g. using Puppeteer/Playwright). It's all Astro and Node.js native, so to speak.
- Can turn any .astro component into an Open Graph image. You don't need to use a preset template and you don't need to mess with html strings in a config file.
- Can re-use Astro components from elsewhere in your site.
- Can make use of Astro props or arbitrary JS in the script section of your template.
Prerequisites
This integration is for Astro. If you aren't using Astro you're in the wrong place.
Installation
In your existing Astro project:
# Using NPM
npx astro add @altano/astro-opengraph
# Using Yarn
yarn astro add @altano/astro-opengraph
# Using PNPM
pnpm astro add @altano/astro-opengraph
Configuration
Configure the integration in your Astro config. You must at least provide some fonts to use (as there are no defaults), e.g. to use Inter installed as an npm package:
export default defineConfig({
integrations: [
opengraphImage({
async getImageOptions() {
return {
fonts: [
{
name: "Inter",
path: "node_modules/@fontsource/inter/files/inter-latin-400-normal.woff",
weight: 400,
style: "normal",
},
],
};
},
}),
],
});
You can find full documentation for the configuration below.
Usage
.astro Component
Create an Astro component to use as your Open Graph image. You can either:
- Create the component outside the pages directory, e.g.
src/components/opengraph.astro
or - Create the component colocated with the endpoint you're going to serve it from, e.g. if the image route is
/blog/article-one/opengraph.png
you can store the component atsrc/pages/blog/article-one/_opengraph.astro
. You must prefix the filename with an underscore ("_") to prevent the .astro component from becoming its own html page.
Example src/components/opengraph.astro
or src/pages/_opengraph.astro
:
<html><body>Hello!</body></html>
See below for limitations on what you can/can't do in your component.
Image Endpoint
In order serve the component from the prior step as an image, you need a js/ts static file endpoint. You can use a static path or dynamic path. You can name the endpoint filename whatever you want as long as it has the extension .png.ts
. For example:
src/pages/opengraph.png.ts
:
import opengraphTemplate from "../components/opengraph.astro";
import { makeOpengraphEndpoint } from "@altano/astro-opengraph/endpoint";
export const GET = makeOpengraphEndpoint({
template: opengraphTemplate,
});
Meta Tags
You'll need <meta>
tags that point to your Open Graph images from your pages. You can add these manually or you can use the included <OpenGraphMeta />
component helper, e.g.:
---
import OpenGraphMeta from "@altano/astro-opengraph/components/meta.astro";
---
<html>
<head>
<OpenGraphMeta />
</head>
<body>
<article><h1>Golfers and Conservatives ā Why Control Was Right</h1></article>
</body>
</html>
This would add the following tags to your <head>
:
<meta property="og:image" content="http://localhost/opengraph.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
You can use the OpenGraphMeta
default props but you probably want to at least provide a title
, description
, and, if you used something other than /opengraph.png.ts
(at the root) as your endpoint, you'll want to specify the path to the image endpoint with the image
prop. For example:
---
import OpenGraphMeta from "@altano/astro-opengraph/components/meta.astro";
---
<html>
<head>
<OpenGraphMeta
image="./opengraph.png"
title="My Cool Website"
description="A page on my website"
/>
</head>
<body>
<article><h1>Golfers and Conservatives ā Why Control Was Right</h1></article>
</body>
</html>
You can also provide an alt
prop which would set the og:image:alt
meta tag, but my understanding is that it isn't well supported, so feel free to omit this.
If you want the OpenGraphMeta
component to use a different set of defaults for your whole site, you can provide the componentMetaTagFallbacks
integration config, e.g. to provide a default title:
export default defineConfig({
integrations: [
opengraphImage({
componentMetaTagFallbacks: {
"og:title": "My Cool Website",
},
async getImageOptions() {
// ...
},
}),
],
});
If provided in the integration configuration like above, <OpenGraphMeta />
will now use "My Cool Website"
as the title default, but if given title via a prop, that will still take precedence.
Putting That Altogether
Once you repeat the steps above for every Open Graph card you want to render, you might have something like this:
src/
āā components/
ā ā āā opengraph-blog.astro
ā ā āā opengraph-root.astro
āā pages/
ā ā āā opengraph.png.ts (imports ../components/opengraph-root.astro)
ā āā blog/
ā ā āā [...slug]/
ā ā ā āā index.astro
ā ā ā āā opengraph.png.ts (imports ../../../components/opengraph-blog.astro)
Or, if you decided to colocate the .astro templates in the pages directory, you might have something like this:
src/
āā pages/
ā āā _opengraph.astro
ā āā opengraph.png.ts (imports ./_opengraph.astro)
ā āā blog/
ā ā āā [...slug]/
ā ā ā āā index.astro
ā ā ā āā _opengraph.astro
ā ā ā āā opengraph.png.ts (imports ./_opengraph.astro)
Configuration Options Reference
The integration requires the following options:
getImageOptions.fonts
: an array of fonts that will be used in your image component. Each font requires:name
: This is whatever you reference in your css, e.g.Inter
path
: A string path to your font file. Can be in your node_modules folder, e.g."node_modules/@fontsource/inter/files/inter-latin-400-normal.woff"
weight
: A weight, from 100 to 900. You can provide different fonts for different weights.
You may also want to provide defaults to be used by the OpenGraphMeta
component in your astro.config.ts
file. These are the defaults you can set:
componentMetaTagFallbacks["og:title"]
: the default title to use in theOpenGraphMeta
component if none is passed in. The integration otherwise defaults to no title.componentMetaTagFallbacks["og:description"]
: the default description to use in theOpenGraphMeta
component if none is passed in. The integration otherwise defaults to no description.componentMetaTagFallbacks["og:image"]
: the default image path to use in theOpenGraphMeta
component. The integration otherwise defaults to"/opengraph.png"
See the TypeScript type-hints and comments for more info.
Examples
Using custom fonts
Run npm install @fontsource/inter
, then:
astro.config.ts
:
import { defineConfig } from "astro/config";
import opengraphImage from "@altano/astro-opengraph";
// https://astro.build/config
export default defineConfig({
integrations: [
opengraphImage({
async getImageOptions() {
return {
fonts: [
{
name: "Inter",
path: "node_modules/@fontsource/inter/files/inter-latin-400-normal.woff",
weight: 400,
style: "normal",
},
{
name: "Inter",
path: "node_modules/@fontsource/inter/files/inter-latin-800-normal.woff",
weight: 800,
style: "normal",
},
],
};
},
}),
],
});
src/components/opengraph.astro
:
<html>
<body style={{ fontFamily: "Inter Variable" }}>
<h1 style={{ fontWeight: 800 }}>
My bold heading
</h1>
<p style={{ fontWeight: 400 }}>
My normal weight paragraph
</p>
</body>
</html>
See https://github.com/altano/npm-packages/tree/main/examples/astro-opengraph for a complete example.
File Locations
It doesn't matter where you put your Open Graph component or endpoints, as long as you point to the correct location from your endpoint and meta tags, e.g. given a component at src/components/my-custom-component.astro
you'd have an endpoint like:
src/pages/my-custom-endpoint.png.ts
:
// import your component, wherever it is
import opengraphTemplate from "../components/my-custom-component.astro";
import { makeOpengraphEndpoint } from "@altano/astro-opengraph/endpoint";
export const GET = makeOpengraphEndpoint({
template: opengraphTemplate,
});
src/pages/index.astro
:
---
import OpenGraphMeta from "@altano/astro-opengraph/components/meta.astro";
---
<html>
<head>
<OpenGraphMeta
<!-- point at your endpoint, wherever it is -->
image="./my-custom-endpoint.png"
/>
</head>
</html>
Dynamic Routes
If you have a dynamic route such as ./src/pages/[...slug].astro
, convert it to ./src/pages/[...slug]/index.astro
Along-side your dynamic route (e.g. ./src/pages/[...slug]/index.astro
) add your Open Graph component and endpoint (e.g. ./src/pages/[...slug]/opengraph.png.ts
):
src/
āā pages/
ā āā [...slug]/
ā ā āā index.astro
ā ā āā _opengraph.astro
ā ā āā opengraph.png.ts
Within opengraph.png.ts
you can either duplicate your implementation of getStaticPaths
or you can re-export the same one used in index.astro
:
opengraph.png.ts
:
export { getStaticPaths } from "./index.astro";
// ...
Catch-all Opengraph Image
To have a default Open Graph image for your whole site, place an Open Graph endpoint at the root of your site (src/pages/opengraph.png.ts
) and then include OpenGraphLayout
in your layout or head component so that every page includes it. Since the OpenGraphLayout
component defaults to finding opengraph.png
at the root of your site, it will use the endpoint you created. Your layout or head component may want to pass-through the OpenGraphLayout
's image
prop for pages that want to override the default. For example:
src/components/Head.astro
:
---
import OpenGraphMeta from "@altano/astro-opengraph/components/meta.astro";
interface Props {
openGraphImage?: string;
}
const { openGraphImage } = Astro.props;
---
<head>
<OpenGraphMeta image={openGraphImage} />
</head>
If you had a page using Head.astro
with the defaults:
src/pages/default.astro
:
---
import Head from "src/components/Head.astro";
---
<html lang="en">
<Head />
</html>
It would have this meta tag:
<meta property="og:image" content="https://example.com/opengraph.png" />
But other pages would be able to override it and use a custom Open Graph image, like so:
src/pages/custom.astro
:
---
import Head from "src/components/Head.astro";
---
<html lang="en">
<Head openGraphImage="./some-other-endpoint.png" />
</html>
Then just make sure that every page uses your Head
component.
You can find an example here: tests/fixtures/catch-all
Notes
CSS Limitations in .astro Template
Your Astro component must be HTML elements and styles supported by Satori, e.g. it can't be stateful or use calc()
in css. The OG Image Playground is a great place to test your component before copying it into your Astro project.
This plugin can only support what Satori supports, which means <style>
tags and CSS imports will NOT work. Your .astro template has to compile to plain html (with inline styles) in order to render correctly. You can reference custom fonts from your inline styles, but they must be provided to the integration config.
Experimental Container APIs
This integration makes use of the experimental container APIs in Astro. Therefore, this plugin is also experimental and may experience breaking changes when you upgrade Astro (even minor or patch releases). This integration itself will remain stable across minor or patch releases.
Troubleshooting
Astro Dev Toolbar
While in astro dev
mode, you can use the included Astro dev toolbar app installed by this integration. It include a preview of the opengraph image as well as the og:
meta tags on the page:
Once your site is live, you can use a tool like opengraph.xyz to preview the opengraph image/tags.
HTML
When creating your .astro template it might be helpful to view the rendered HTML in a browser so that you can use the browser dev tools to diagnose CSS-or-whatever issues. To do that, you can create a dev-only html endpoint for your template similar to how you make the PNG one. This development-only will not produce ssg output and will not work in ssr.
import opengraphTemplate from "./_opengraph.astro";
import { makeOpengraphDevEndpoint } from "../../../../../src/endpoint.ts";
import type { APIRoute } from "astro";
export const GET: APIRoute = makeOpengraphDevEndpoint({
template: opengraphTemplate,
});
FAQ
Question: How does this work?
This integration uses Astro's container API to convert your .astro component into raw html, and relies on Vercel's Satori library to convert that html into an svg. It then uses the Astro image service to convert that into a png.
This integration mostly just serves as a glue between these existing APIs.
Question: Why is this so hard to configure?
Answer: Astro does not allow .astro components to be statically built as anything other than HTML, so converting an .astro component into an image requires a static file endpoint (specifically a png image endpoint).
This integration could theoretically add image endpoints for you, and that would work for one-off endpoints like a single opengraph.png file at the root of your site, but Astro does not give integrations the level of control required to dynamically register endpoints that match your other dynamic routes.
I've made a proposal to allow Astro components to render to file types other than HTML which would simplify this integration (you would be able to name your .astro template with a .png.astro
extension to have it be rendered as a png image) but that proposal is stalled and unlikely to go anywhere.
In short, until we get new APIs, this is as ergonomic an API as you're going to get for generating Open Graph images in Astro.
6 months ago
6 months ago
6 months ago
6 months ago