Edge runtime
Satori + Next.js OG images — the 3 errors that cost me 4 hours
Posted July 15, 2026 · 7 min read
Every Next.js SaaS needs Open Graph images — they're the preview cards when someone shares your URL on Twitter, Slack, iMessage, or LinkedIn. The Vercel docs show you ImageResponse + Satoriand say "it just works." It doesn't. Here are the 3 errors that bit me at 2am, with the fix for each.
Error 1: app/api/og/route.tsx returns 405
The Vercel docs suggest creating OG images as API routes. They don't tell you that route.tsxunder app/api/ is treated as an API route handler — and API routes only respond to GET if you export a GET function. ImageResponse doesn't ship as a GET handler correctly.
Fix: use a route segment file convention, not an API route. Place at app/og/opengraph-image.tsx or app/[param]/opengraph-image.tsx. Next.js recognizes the special filename and serves it as a static image at /og/....
Error 2: searchParams: Promise<...> 500s in Satori
In Next.js 15, page params and searchParams became async. You await them in pages and route segments. But Satori's ImageResponse doesn't await them — it expects the values synchronously. Wait, no — the problem is the opposite. If you don't await them in Satori, you pass an unresolved Promise to JSX, which throws a non-obvious 500 when Satori tries to read string properties.
Fix: use dynamic route segments with params (synchronous), not query strings. The route file app/og/[param]/opengraph-image.tsx receives params as a plain object (synchronous in edge runtime).
// app/og/[param]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export const alt = 'Open Graph image';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export default function Image({ params }: { params: { param: string } }) {
return new ImageResponse(
<div style={{
width: '100%', height: '100%',
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: '#0a0e14', color: '#fff', fontSize: 48,
}}>
{params.param}
</div>
);
}Error 3: @supabase/supabase-js doesn't fit in Edge runtime
The biggest OG image I wanted to render pulled user data from Supabase. The official @supabase/supabase-jspackage is too big — it adds 300KB+ to your edge bundle and exceeds the 1MB edge function limit when combined with Satori's font assets. Build fails with cryptic "module not found" or 500 errors at runtime.
Fix: use direct fetch against Supabase's REST API instead of the SDK. Edge runtime has native fetch; you don't need the SDK.
const res = await fetch(
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/profiles?username=eq.${username}&select=name,bio`,
{
headers: {
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`,
},
}
);
const [profile] = await res.json();Save this as a helper, use it in every dynamic OG image, and the bundle stays under 200KB.
Bonus: serving fonts in Edge
Satori needs fonts. If you import a font file at the top of your edge route file, Next.js bundles it — but some font files exceed edge memory limits. Two fixes:
- Use Google Fonts CDN instead of bundling —
fetch('https://fonts.googleapis.com/...')returns the font, Satori accepts aBufferdirectly. - Use the same font for everything — pick one font and reuse it via shared helper. Saves edge memory.
Per-page OG images with metadata
Once you have the dynamic OG route set up, link it from each page's metadata:
// app/[username]/page.tsx
export async function generateMetadata({ params }) {
return {
title: `${params.username} on FitForge`,
openGraph: {
images: [`/og/${params.username}`], // → edge route renders dynamically
},
};
}When someone shares yourdomain.com/alice, the link preview pulls from your dynamic OG route, fetches Alice's profile, and renders their name. Looks intentional every time.
The complete OG image system (per-user, per-launch, with Supabase fetch pattern + metadata linking) is in Forge Stack. Tested on production traffic to FitForge.
Tags: #nextjs #satori #ogimage #edge #social