The future of JavaScript on the server is NOT just NodeJS. We have Bun, Deno, and Cloudflare.
I've been trying to get Vercel's og/image package to deploy outside of NextJS and Vercel for a long time; particularly I wanted it to work on SvelteKit and Cloudflare.
But... finally... I mean, finally... we have it.
TL;DR
I provide demo and source code for getting og/image to work in NextJS, SvelteKit, Nuxt, and AnalogJS in different edge environments.
Let's see what every option for deployment and framework looks like:
Serverless Functions
If you're deploying to regular Serverless functions that use NodeJS, you shouldn't have any problems with packages.
-
NextJS -
next/ogorvercel/og -
AnalogJS -
@analogjs/content/og -
SvelteKit -
@ethercorps/sveltekit-og- Can pass Svelte Component -
QwikJS -
og-img -
SolidStart -
og-img -
Astro -
og-img- see also images -
Nuxt -
nuxt-og-image- predefined image, no customization -
Cloudflare -
workers-og- No Framework
NextJS and Vercel Edge
@vercel/og was built for NextJS, so it works out of the box. Use vercel/og for pages directory, or next/og for the new app directory API endpoint.
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET() {
try {
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'white',
padding: '40px',
}}
>
<div
style={
{
fontSize: 60,
fontWeight: 'bold',
color: 'black',
textAlign: 'center',
}
}
>
Welcome to My Site
</div>
< div
style={{
fontSize: 30,
color: '#666',
marginTop: '20px',
}
}
>
Generated with Next.js ImageResponse
</div>
</div>
),
{
width: 1200,
height: 630,
}
)
} catch (e) {
console.log(`${(e as Error).message}`)
return new Response(`Failed to generate the image`, {
status: 500,
})
}
}
Repo: GitHub
Demo: Vercel Edge
As you can imagine, it works out of the box!
NextJS and Cloudflare
In order to use NextJS on Cloudflare, you must install OpenNext. og/next also works here out of the box!
With the core original ImageResponse plugin, you can use tailwind instead of styles with the tw attribute.
import { ImageResponse } from "next/og";
export async function GET() {
return new ImageResponse(
<div tw="flex h-full w-full flex-col items-center justify-center
bg-white p-10">
<div tw="text-center text-[60px] font-bold text-black">
Welcome to My Site
</div>
<div tw="mt-5 text-[30px] text-gray-600">
Generated with NextJS ImageResponse and deployed to Cloudflare
</div>
</div>
);
}
Repo: GitHub
Demo: Cloudflare
SvelteKit and Vercel Edge
Recently, the creator of @cf-wasm/og was kind enough to get it working for SvelteKit!
I copied the ideas from @ethercorps/sveltekit-og and made it so you can pass in a Svelte component directly!
image-card.svelte
You can call the component whatever you want. This package also uses satori-html, which automatically converts class with tailwind, to tw and react JSX under the hood. You can create variables, and pass them as well if you like!
<script lang="ts">
const { title, website }: { title?: string; website?: string } = $props();
</script>
<div
class="bg-slate-50 text-slate-700 w-full h-full flex items-center
justify-center p-6"
>
<div
class="m-1.5 p-6 w-full h-full rounded-3xl text-[72px] flex flex-col
border-2 border-slate-700 text-slate-700"
>
{title?.slice(0, 80) || "Default Title"}
<hr class="border border-slate-700 w-full" />
<p class="text-[52px] font-bold flex justify-center text-slate-700">
{website || "Default Website"}
</p>
</div>
</div>
image-response.ts
import type { Component } from 'svelte';
import { render } from 'svelte/server';
import { ImageResponse as OGImageResponse } from '@cf-wasm/og';
import { html } from 'satori-html';
export const prerender = false;
export const ImageResponse = async <T extends Record<string, unknown>>(
component: Component<T>,
options?: ConstructorParameters<typeof OGImageResponse>['1'],
props?: T
) => {
const result = render(component as Component, { props });
return await OGImageResponse.async(html(result.body), options);
};
/og/+server.ts
import { type RequestHandler } from "@sveltejs/kit";
import { ImageResponse } from "$lib/image-response";
import ImageCard from "$lib/image-card.svelte";
export const prerender = false;
export const GET = (async ({ url }) => {
const { width, height } = Object.fromEntries(url.searchParams);
return await ImageResponse(
ImageCard,
{
width: Number(width) || 1600,
height: Number(height) || 900
},
{
title: 'Custom OG Image',
website: 'Generated with Svelte, Vercel, and Cloudflare WASM!'
}
);
}) satisfies RequestHandler;
@ethercorps/sveltekit-og also now works on the Edge, but I wouldn't recommend it until the bundle is smaller problem is fixed.
Demo: Vercel Edge
Repo: GitHub
As a SvelteKit developer, I'm supper happy about this one!
SvelteKit and Cloudflare
You can also get it to work on Cloudflare with a few caveats.
Import from workered
import { ImageResponse as OGImageResponse } from '@cf-wasm/og/workerd';
Use Custom Vite Plugin
import tailwindcss from '@tailwindcss/vite';
import devtoolsJson from 'vite-plugin-devtools-json';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
import cloudflareModules from '@cf-wasm/plugins/vite-additional-modules';
export default defineConfig({
ssr: {
noExternal: [/@cf-wasm\/.*/]
},
plugins: [
tailwindcss(),
sveltekit(),
devtoolsJson(),
cloudflareModules({ target: "edge-light" })
]
});
Repo: GitHub
Demo: Cloudflare
Nuxt and Vercel Edge
The Nuxt-SEO plugin works out of the box, however, there is a current bug where it won't deploy on Cloudflare or Vercel Edge correctly. You can downgrade to v5.1.9 to get this to work. However, it is a premade image that does not allow you to customize the style.
Thankfully, you can use the same @cf-wasm/og plugin to work in Nuxt!
OG Component
Like the SvelteKit version, you can write you code directly in a Vue Component!
<script setup lang="ts">
defineProps<{ title: string }>()
</script>
<template>
<div
class="flex h-full w-full flex-col items-center justify-center
bg-white p-10"
>
<div class="text-center text-[60px] font-bold text-black">
{{ title }}
</div>
<div class="mt-5 text-[30px] text-gray-600">
Generated with Nuxt ImageResponse and deployed to Vercel Edge
</div>
</div>
</template>
og.get.ts
You must import the component and render it in the server endpoint.
import { ImageResponse } from '@cf-wasm/og';
import { html } from 'satori-html';
import { createSSRApp, h } from 'vue';
import { renderToString } from 'vue/server-renderer';
import Og from '~/components/og.vue';
export default defineEventHandler(async () => {
const app = createSSRApp({
render: () => h(Og, { title : 'Welcome to My Image' })
})
const data = await renderToString(app)
return ImageResponse.async(html(data), {
width: 1200,
height: 630
})
})
Nuxt Config
Make sure to install, and add the @vitejs/plugin-vue and use the @cf-wasm/plugin.
// https://nuxt.com/docs/api/configuration/nuxt-config
import additionalModules from "@cf-wasm/plugins/nitro-additional-modules"
import vue from "@vitejs/plugin-vue"
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
nitro: {
preset: 'vercel-edge',
modules: [additionalModules({ target: "edge-light" })],
rollupConfig: {
plugins: [vue()]
}
}
})
Repo: GitHub
Demo: Vercel Edge
Nuxt and Cloudflare
You can deploy to Cloudflare with a small change:
import { ImageResponse } from '@cf-wasm/og';
and
// https://nuxt.com/docs/api/configuration/nuxt-config
import additionalModules from "@cf-wasm/plugins/nitro-additional-modules"
import vue from "@vitejs/plugin-vue"
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
nitro: {
preset: 'cloudflare-module',
modules: [additionalModules({ target: "edge-light" })],
rollupConfig: {
plugins: [vue()]
}
}
})
Repo: GitHub
Demo: Cloudflare
AnalogJS and Cloudflare
Analog works similarly thanks to @cf-wasm/og again!
Vite Config
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import analog from '@analogjs/platform';
import tailwindcss from '@tailwindcss/vite';
import additionalModules from "@cf-wasm/plugins/nitro-additional-modules"
// https://vitejs.dev/config/
export default defineConfig(() => ({
build: {
target: ['es2020'],
},
resolve: {
mainFields: ['module'],
},
plugins: [
analog({
ssr: true,
static: false,
nitro: {
preset: 'cloudflare-module',
cloudflare: {
deployConfig: true,
nodeCompat: true
},
modules: [additionalModules({ target: "edge-light" })],
compatibilityDate: "2025-07-15"
}
}),
tailwindcss()
]
}));
and import from:
import { ImageResponse } from '@cf-wasm/og';
Repo: GitHub
Demo: Cloudflare
AnalogJS and Vercel Edge
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import analog from '@analogjs/platform';
import tailwindcss from '@tailwindcss/vite';
import additionalModules from "@cf-wasm/plugins/nitro-additional-modules"
// https://vitejs.dev/config/
export default defineConfig(() => ({
build: {
target: ['es2020'],
},
resolve: {
mainFields: ['module'],
},
plugins: [
analog({
ssr: false,
static: false,
nitro: {
preset: 'vercel-edge',
modules: [additionalModules({ target: "edge-light" })],
compatibilityDate: "2025-07-15"
}
}),
tailwindcss()
]
}));
Repo: GitHub
Demo: Vercel Edge
Bonus - SvelteKit with Vercel Bun
Vercel bun works as expected with @vercel/og! No workarounds. However, there is currently a satori-html Bun bug if you're NOT using JSX. I assume it will be fixed in the coming weeks. Either way, Vercel Bun is currently still experimental.
I used AI to create a small satori parser, but don't count on it to work for anything complicated.
Image Response
import type { Component } from 'svelte';
import { render } from 'svelte/server';
import { ImageResponse as OGImageResponse } from '@vercel/og';
import { htmlToOgStrict as html } from './parse-html';
//import { html } from 'satori-html';
// https://github.com/oven-sh/bun/pull/15047
export const prerender = false;
export const ImageResponse = <T extends Record<string, unknown>>(
component: Component<T>,
options?: ConstructorParameters<typeof OGImageResponse>['1'],
props?: T
) => {
const result = render(component as Component, { props });
return new OGImageResponse(html(result.body), options);
};
Repo: GitHub
Demo: Vercel Bun
Deno Deployments
Netlify and Supabase Edge Functions currently use Deno under the hood.
Deno has an ImageResponse that works in Supabase and should equally work for Netlify deployments.
I didn't create a demo, but you shouldn't have any issues.
Other Frameworks
I have not tested anything on SolidStart, TanStack Start, or React Router. I am personally not a React fan, but it is probably easier to get them to work than other frameworks. SolidStart uses Nitro, so it should be similar. Qwik 2 will probably use Nitro when it is released as well.
Please let me know if you test other frameworks in non-Node environments, and I will post it here.
Vercel Edge Sunsetting?
It has recently come to my attention that Vercel Edge is going away, sort of. SvelteKit and Nitro have marked the Edge option as soon to be depreciated.
What is interesting to me, is there is nothing in NextJS about depreciation, BUT there is a recommendation to migrate away from the Edge Runtime.
According to a Vercel team member, it will probably never go away, but will just stick in maintenance mode... which maybe the same thing.
Summary
You can now deploy everywhere the og/image, there just may be a few configurations.
Thank you Deo for your hard work on this and helping me debug!
J









Top comments (0)