How To Make A Dynamic Image Generator For Your Website (Obsidian Digital Garden Example)
When you share links to your digital garden notes, you want them to stand out. Generic previews are easily missed. This guide details how to create dynamic, custom preview images (like Open Graph images) for each note, automatically showing its title and even an image from the note itself. We'll build this for a Netlify-hosted Eleventy site, but the core ideas are adaptable for other hosting providers (e.g., Vercel is used at the end).
Video Walkthrough that accompanies this guide:
The Goal: When a link to any note is shared (on Farcaster, Twitter, Discord, etc.), a unique, informative image is automatically generated and displayed in the link preview, making your content more engaging.
Note: I have made a copy of this guide with more details, along with the exact code I generated during this tutorial, available to my paying YouTube members. If you would like to have these assets, please consider joining my paid membership.
My Approach: An On-Demand Image Generation Service with Netlify Edge Functions
The core of our solution is a small "image generation service" that runs as a Netlify Edge Function. Edge Functions are pieces of code that run on Netlify's global network, close to the user requesting the image, making them fast. This service will take details about a specific note (like its title) and create a custom preview image on the fly.
Let's Build It, Step-by-Step:
Step 1: Crafting the Image Generation Function (The "Generator")
This JavaScript file is where the magic happens. It will live in your project and tell Netlify how to create the images.
-
File Setup:
- In your project, create a folder path:
netlify/edge-functions/
. - Inside
edge-functions/
, create a file namedog-image-generator.js
.
- In your project, create a folder path:
-
What the Generator Does:
- Listen for Requests: We'll configure it to respond to a specific web address, like
YOUR_SITE.com/api/og-image
. When this address is called, the generator wakes up. - Gather Information: It looks at the address it was called with to find details like the note's
title
and an optionalbgImageUrl
(if the note contains an image to use as a background). These are passed as parameters (e.g.,.../api/og-image?title=My+Note&bgImageUrl=...
). - Load Custom Fonts: To make the text look professional, we'll use specific fonts (B612 Bold for titles, Open Sans for other text like the author name). The generator needs access to these font files. We'll make these files part of your main website build, and the generator will fetch them from your live site (using its current domain, whether it's a preview or production).
- Choose a Layout & Style:
- If a
bgImageUrl
is provided: The generator uses this image as the main background for the preview. To ensure the title text is readable, a subtle dark gradient is overlaid on the bottom portion of the background image. - If no
bgImageUrl
is provided: The generator uses a clean, solid dark blue background.
- If a
- Draw the Elements: Using a specialized image creation library (
ImageResponse
viaog_edge
), the generator then "draws" the visual elements:- The chosen background (image with gradient or solid blue).
- The note's title, prominently displayed (using B612 Bold font).
- Your site name or author handle (e.g., "wanderloots.eth" in Open Sans font), placed subtly.
- Your site's favicon, in a corner.
- Send the Image: The final output is a 600x400 pixel image, sent back to whatever requested it (like Farcaster or Twitter).
- Listen for Requests: We'll configure it to respond to a specific web address, like
-
Telling Netlify the Address: Inside the
og-image-generator.js
file itself, a small piece of configuration tells Netlify which web path triggers this function:export const config = { path: "/api/og-image" };
Step 2: Configuring Netlify to Run the Generator (netlify.toml
)
This file at the root of your project gives Netlify build and deployment instructions.
- Key Settings to Add/Verify:
- Under the
[build]
section:publish = "dist"
(or your Eleventy output folder).command = "npm install && npm run build"
(or your site's build command).edge_functions = "netlify/edge-functions"
: This tells Netlify where to find your generator script.
- Under a
[functions."*"]
section (or[functions."og-image-generator"]
if you prefer to be specific):included_files = ["./netlify/edge-functions/fonts/**"]
: This is vital! It tells Netlify to package your font files (which you'll place in anetlify/edge-functions/fonts/
folder next to your script) along with the generator function. This ensures the Deno runtime can access them locally usingDeno.readFile()
, which proved more reliable than fetching them over HTTP.
- Remove Conflicting Redirects: Ensure there are no
[[redirects]]
rules in yournetlify.toml
that also try to handle the/api/*
path, as the Edge Function'sconfig
now manages this. Your 404 redirect can stay.
- Under the
Step 3: Preparing Your Eleventy Website for Dynamic Previews
Your Eleventy site (the static site generator) needs a few helpers to provide the right information to the image generator.
-
Making Font Files Available:
- Action: Create a folder in your Eleventy source, for example,
src/site/assets/fonts/
. - Action: Place your
.ttf
font files (e.g.,B612-Bold.ttf
,OpenSans-Regular.ttf
) into this new folder. - Eleventy Config (
.eleventy.js
or.eleventy.cjs
): Add an instruction to copy this entirefonts
folder to your final built site (e.g., intodist/assets/fonts/
). This is done with:
(This step also makes the fonts available for the image generator to fetch if using the public URL method, but witheleventyConfig.addPassthroughCopy("src/site/assets/fonts");
included_files
andDeno.readFile
, it's mainly for site consistency).
- Action: Create a folder in your Eleventy source, for example,
-
Finding the First Image in a Note (Eleventy Filter):
- Goal: If a note contains images, we want to use the first one as the background for our preview.
- Eleventy Config: Create a custom filter (e.g.,
getFirstImageURL
). This filter takes the HTML content of a note, parses it to find the web address (src
) of the first<img>
tag, and returns its full URL. If no image is found, it returns nothing.
-
Making Titles URL-Safe (Eleventy Filter):
- Goal: Note titles can have spaces or special characters. These need to be "encoded" to be safely included in a web address.
- Eleventy Config: Add a filter named
urlencode
that performs this encoding (e.g., spaces become%20
).
Step 4: Updating Your Note Page Template (note.njk
)
This is where you instruct each note page to use the dynamic image generator.
- Inside the
<head>
section ofsrc/site/_includes/layouts/note.njk
:-
Prepare Variables (using Nunjucks templating):
- Get the current note's title (or its filename if no title is set).
- Use your
urlencode
filter to make this title URL-safe. - Use your
getFirstImageURL
filter on the note's maincontent
to find a potential background image URL. If found, URL-encode it too. - Construct the full
absoluteDynamicImageUrl
for the image generator:- Start with your website's full base URL (e.g.,
https://YOUR_SITE.com
). - Append
/api/og-image?title=ENCODED_TITLE
. - If a background image was found, append
&bgImageUrl=ENCODED_BG_IMAGE_URL
.
- Start with your website's full base URL (e.g.,
-
Add Standard Open Graph (OG) Meta Tags: These are for general social sharing (Twitter, Discord, Facebook, etc.).
<meta property="og:title" content="YOUR_NOTE_TITLE_HERE_FROM_ELEVENTY_VARIABLE"> <meta property="og:image" content="{{ absoluteDynamicImageUrl }}"> <meta property="og:image:width" content="600"> <meta property="og:image:height" content="400"> <meta property="og:url" content="FULL_URL_TO_THIS_NOTE_PAGE_FROM_ELEVENTY_VARIABLE">
-
Add Farcaster Frame Meta Tag: This is specifically for Farcaster.
<meta name="fc:frame" content='{"version":"next","imageUrl":"{{ absoluteDynamicImageUrl }}", ...other Farcaster frame details like button text and action URL... }'>
Notice that both
og:image
andfc:frame:imageUrl
use the sameabsoluteDynamicImageUrl
we constructed.
-
The Result:
With these pieces in place, every note page on your Eleventy site will now have meta tags pointing to your dynamic image generator. When a link to a note is shared:
- The sharing platform (Farcaster, Twitter, etc.) reads these meta tags.
- It calls the unique URL in the
imageUrl
orog:image
tag. - Your Netlify Edge Function (
og-image-generator.js
) receives the request, sees the title (and possibly background image URL) in the parameters, loads the fonts, and generates a custom image. - This custom image is sent back and displayed in the link preview.
A Note on Image Positioning (The Tricky Part I Solved):
When I first tried using a background image from the note, I faced a challenge: the image appeared extremely zoomed-in and stuck to the top-left corner, not nicely centered and covering the whole area.
- The Problem: The image creation library (
@vercel/og
and its Satori rendering engine) didn't fully support standard CSS properties likebackground-position: center
orobject-position: center
in the way a web browser does. - Initial Attempts: I tried various CSS tricks, like using
background-image
on adiv
, then switching to an<img>
tag withobject-fit: cover
. These still resulted in the incorrect top-left zoom. One common CSS centering trick even made the image disappear entirely! - The Solution That Worked: I reverted to using an actual
<img>
tag for the background, positioned absolutely to fill the entire preview image area (top:0, left:0, width:'100%', height:'100%'
). I then set its style toobject-fit: 'cover'
andobject-position: 'center'
. The key was ensuring its parent container (mainContainerStyle
) was transparent when a background image was present (by settingmainContainerStyle.background = 'transparent'
). This allowed the<img>
tag to be fully visible. Whileobject-position: center
might still not be perfectly honored by the underlying renderer, this combination provided the most acceptable "cover and center" behavior I could achieve, significantly reducing the extreme zoom.
Adapting for Other Platforms (like Vercel):
The beauty of this approach is its portability. If you were using Vercel:
- The image generator script (
og-image-generator.js
) would go into Vercel'sapi/
directory. Vercel uses file-based routing, soapi/og-image.js
would automatically become available at the/api/og-image
path. - You wouldn't need
netlify.toml
. Vercel typically auto-detects Eleventy or uses avercel.json
for build configurations. Font files would be deployed as part of your static assets and fetched via their public Vercel URL. - The Eleventy configuration (filters, passthrough copies) and the
note.njk
template modifications would remain exactly the same.
The core logic for generating the image is the same; only the platform-specific function deployment and asset serving details change.