diff --git a/examples/app-router/app/api/og/route.tsx b/examples/app-router/app/api/og/route.tsx
new file mode 100644
index 000000000..8f19cb8bf
--- /dev/null
+++ b/examples/app-router/app/api/og/route.tsx
@@ -0,0 +1,72 @@
+import { ImageResponse } from "next/og";
+// App router includes @vercel/og.
+// No need to install it.
+// ?title=
+
+export async function GET(request: Request) {
+  try {
+    const { searchParams } = new URL(request.url);
+
+    // ?title=
+    const hasTitle = searchParams.has("title");
+    const title = hasTitle
+      ? searchParams.get("title")?.slice(0, 100)
+      : "My default title";
+
+    return new ImageResponse(
+      
+        
+          

+        
+        
+          {title}
+        
+      
+      OpenNext
+    
,
+    // ImageResponse options
+    {
+      // For convenience, we can re-use the exported opengraph-image
+      // size config to also set the ImageResponse's width and height.
+      ...size,
+    },
+  );
+}
diff --git a/examples/app-router/app/og/page.tsx b/examples/app-router/app/og/page.tsx
new file mode 100644
index 000000000..0b98f2d64
--- /dev/null
+++ b/examples/app-router/app/og/page.tsx
@@ -0,0 +1,3 @@
+export default function Page() {
+  return ;
+}
diff --git a/packages/tests-e2e/tests/appRouter/og.test.ts b/packages/tests-e2e/tests/appRouter/og.test.ts
new file mode 100644
index 000000000..734d99b56
--- /dev/null
+++ b/packages/tests-e2e/tests/appRouter/og.test.ts
@@ -0,0 +1,60 @@
+import { createHash } from "node:crypto";
+import { expect, test } from "@playwright/test";
+
+// This is the md5sums of the expected PNGs generated with `md5sum `
+const OG_MD5 = "6e5e794ac0c27598a331690f96f05d00";
+const API_OG_MD5 = "cac95fc3e2d4d52870c0536bb18ba85b";
+
+function validateMd5(data: Buffer, expectedHash: string) {
+  return createHash("md5").update(data).digest("hex") === expectedHash;
+}
+
+test("Open-graph image to be in metatags and present", async ({
+  page,
+  request,
+}) => {
+  await page.goto("/og");
+
+  // Wait for meta tags to be present
+  const ogImageSrc = await page
+    .locator('meta[property="og:image"]')
+    .getAttribute("content");
+  const ogImageAlt = await page
+    .locator('meta[property="og:image:alt"]')
+    .getAttribute("content");
+  const ogImageType = await page
+    .locator('meta[property="og:image:type"]')
+    .getAttribute("content");
+  const ogImageWidth = await page
+    .locator('meta[property="og:image:width"]')
+    .getAttribute("content");
+  const ogImageHeight = await page
+    .locator('meta[property="og:image:height"]')
+    .getAttribute("content");
+
+  // Verify meta tag exists and is the correct values
+  expect(ogImageSrc).not.toBe(null);
+  expect(ogImageAlt).toBe("OpenNext");
+  expect(ogImageType).toBe("image/png");
+  expect(ogImageWidth).toBe("1200");
+  expect(ogImageHeight).toBe("630");
+
+  // Check if the image source is working
+  const response = await request.get(`/og/${ogImageSrc?.split("/").at(-1)}`);
+  expect(response.status()).toBe(200);
+  expect(response.headers()["content-type"]).toBe("image/png");
+  expect(response.headers()["cache-control"]).toBe(
+    "public, immutable, no-transform, max-age=31536000",
+  );
+  expect(validateMd5(await response.body(), OG_MD5)).toBe(true);
+});
+
+test("next/og (vercel/og) to work in API route", async ({ request }) => {
+  const response = await request.get("api/og?title=opennext");
+  expect(response.status()).toBe(200);
+  expect(response.headers()["content-type"]).toBe("image/png");
+  expect(response.headers()["cache-control"]).toBe(
+    "public, immutable, no-transform, max-age=31536000",
+  );
+  expect(validateMd5(await response.body(), API_OG_MD5)).toBe(true);
+});