diff --git a/docs/docs.json b/docs/docs.json
index d36e0dba..ab0f1614 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -454,7 +454,9 @@
"group": "Technical Guides",
"pages": [
"mini-apps/technical-guides/sign-manifest",
+ "mini-apps/technical-guides/dynamic-embeds",
"mini-apps/technical-guides/neynar-notifications"
+
]
},
{
diff --git a/docs/mini-apps/technical-guides/dynamic-embeds.mdx b/docs/mini-apps/technical-guides/dynamic-embeds.mdx
new file mode 100644
index 00000000..539f4fef
--- /dev/null
+++ b/docs/mini-apps/technical-guides/dynamic-embeds.mdx
@@ -0,0 +1,200 @@
+---
+title: Generate Dynamic Embed Images
+description: Create viral loops by turning every user interaction into dynamic, shareable content directly in the feed.
+---
+
+Embeds are the first thing users see when they encounter your mini app in their feed. Each share can display unique, contextual content tailored to drive engagement.
+
+
+
+
+
+When users share your mini app `URL`, the Base app requests your page, reads the fc:miniapp metadata, and fetches the `imageUrl`. You can serve either a static file (same image for everyone) or a dynamic endpoint that generates unique images on-demand based on URL parameters.
+
+
+
+This guide uses Minikit but the principles apply to any framework with server-side rendering.
+
+
+## Implementation
+
+This guide shows how to create shareable links with dynamic embed images. Users click a share button, which opens a compose window with their personalized link. When shared, the embed displays a unique image with their username.
+
+
+
+Install `@vercel/og` by running the following command inside your project directory. This isn't required for Next.js App Router projects, as the package is already included:
+
+```bash
+npm install @vercel/og
+```
+
+
+Build an API route that generates images based on the username parameter.
+
+```tsx lines expandable wrap app/api/og/[username]/route.tsx
+import { ImageResponse } from "next/og";
+
+export const dynamic = "force-dynamic";
+
+export async function GET(
+ request: Request,
+ { params }: { params: Promise<{ username: string }> }
+) {
+ const { username } = await params;
+
+ return new ImageResponse(
+ (
+
+ Hello {username}
+
+ ),
+ {
+ width: 1200,
+ height: 630,
+ }
+ );
+}
+```
+
+This endpoint generates a unique image for each username: `/api/og/alice`, `/api/og/bob`, etc.
+
+
+ `
` elements must have `display: "flex"` or `display: "none"`. If you see a 500 error when accessing `/share/[username]`, check your ImageResponse JSX structure.
+
+
+
+
+Build a page route that uses the username to generate `fc:miniapp` metadata pointing to your image endpoint.
+
+```tsx lines expandable wrap app/share/[username]/page.tsx
+import { minikitConfig } from "../../../minikit.config";
+import { Metadata } from "next";
+
+export async function generateMetadata(
+ { params }: { params: Promise<{ username: string }> }
+): Promise {
+ try {
+ const { username } = await params;
+
+ return {
+ title: minikitConfig.miniapp.name,
+ description: minikitConfig.miniapp.description,
+ other: {
+ "fc:miniapp": JSON.stringify({
+ version: minikitConfig.miniapp.version,
+ imageUrl: `${minikitConfig.miniapp.homeUrl}/api/og/${username}`,
+ button: {
+ title: `Join the ${minikitConfig.miniapp.name} Waitlist`,
+ action: {
+ name: `Launch ${minikitConfig.miniapp.name}`,
+ type: "launch_frame",
+ url: `${minikitConfig.miniapp.homeUrl}`,
+ },
+ },
+ }),
+ },
+ };
+ } catch (e) {
+ const errorMessage = e instanceof Error ? e.message : 'Unknown error';
+ console.log(JSON.stringify({
+ timestamp: new Date().toISOString(),
+ level: 'error',
+ message: 'Failed to generate metadata',
+ error: errorMessage
+ }));
+
+ return {
+ title: minikitConfig.miniapp.name,
+ description: minikitConfig.miniapp.description,
+ };
+ }
+}
+
+export default async function SharePage(
+ { params }: { params: Promise<{ username: string }> }
+) {
+ const { username } = await params;
+
+ return (
+
+
Share Page - {username}
+
+ );
+}
+```
+When someone visits `/share/alice`, the metadata points to `/api/og/alice` for the embed image.
+
+
+
+Create a button that opens Farcaster's compose window with the user's personalized share link.
+
+```tsx lines expandable wrap app/page.tsx highlight={6, 9-15}
+import { useMiniKit, useComposeCast } from "@coinbase/onchainkit/minikit";
+import { minikitConfig } from "./minikit.config";
+
+export default function HomePage() {
+ const { context } = useMiniKit();
+ const { composeCast } = useComposeCast();
+
+
+ const handleShareApp = () => {
+ const userName = context?.user?.displayName || 'anonymous';
+ composeCast({
+ text: `Check out ${minikitConfig.miniapp.name}!`,
+ embeds: [`${window.location.origin}/share/${userName}`]
+ });
+ };
+
+ return (
+
+
+
+ );
+}
+```
+
+When you click the button, it opens the compose window with `/share/alice` as the embed. The embed displays the dynamic image from `/api/og/alice`.
+
+
+
+Verify the complete sharing flow works.
+
+```bash lines wrap
+# Start your app
+npm run dev
+
+# Test the image endpoint directly
+curl http://localhost:3000/api/og/testuser > test.png
+open test.png
+
+# Visit the share page to verify metadata
+curl http://localhost:3000/share/testuser | grep "fc:miniapp"
+```
+
+Click the share button in your app to test the full experience. You should see the compose window open with your personalized share link, and the embed should display your custom generated image.
+
+
+
+## Related Concepts
+
+
+
+ Troubleshooting tips for embeds not displaying.
+
+
diff --git a/docs/mini-apps/technical-guides/embeds-and-previews.mdx b/docs/mini-apps/technical-guides/embeds-and-previews.mdx
deleted file mode 100644
index 8c7a363b..00000000
--- a/docs/mini-apps/technical-guides/embeds-and-previews.mdx
+++ /dev/null
@@ -1,272 +0,0 @@
----
-title: Embeds & Previews
-description: Turn every user action into organic growth with shareable, metadata-powered embeds that drive engagement and discovery.
----
-
-> **What you'll learn**
-> By the end of this guide, you'll be able to:
-> - Understand how embeds and metadata work together to create rich social previews
-> - Choose between static and dynamic embeds for different use cases
-> - Debug and optimize embeds for maximum performance and engagement
-
-## Why Sharing Matters
-
-Sharing is one of the fastest and most cost-effective ways to grow your Mini App user base.
-
-When a user shares your app into a feed (such as Base App or Farcaster), the platform generates a **rich embed** — a visual preview complete with your branding, imagery, and call-to-action button that appears directly in social feeds.
-
-Every share:
-
-- **Increases reach** — friends and followers see your app instantly
-- **Drives engagement** — visually compelling embeds get more clicks than plain links
-- **Improves ranking** — shared apps are more likely to appear in category leaderboards
-- **Creates viral loops** — great experiences encourage users to share with their networks
-
-## Metadata and Embeds
-
-### How Metadata Creates Embeds
-
-When someone shares your Mini App link, platforms like Base App don't just show a plain URL. Instead, they fetch **metadata** from your page and use it to generate a rich **embed** — a visual preview card with your image, title, and call-to-action button.
-
-The metadata acts as instructions that tell the platform exactly how to display your Mini App in feeds.
-
-
-
-
-
-**The complete metadata-to-embed process:**
-
-
-
- User clicks share or pastes your Mini App URL into a social feed (Base App, Farcaster).
-
-
-
- The platform makes a request to your URL and reads the `` tags in your HTML to understand how to display your app.
-
-
-
- Platform transforms your metadata into a rich visual embed with image, title, description, and interactive button.
-
-
-
- Your Mini App appears as an attractive, clickable card that users can launch directly from their feed.
-
-
-
-### Metadata Structure
-
-Your metadata consists of specific HTML meta tags that define each part of the embed:
-
-```html index.html
-
-```
-
-Each piece of metadata directly corresponds to a visual element in the embed:
-
-- `imageUrl` → The main visual that appears in the embed
-- `button.title` → Text on the call-to-action button
-- `action.name` → App name displayed in the embed
-- `action.url` → Where users go when they click the embed
-
-### Embed Appearance in Feeds
-
-
-
-
-
-### Manifest vs Embed Metadata
-
-Your Mini App uses two types of metadata:
-
-#### Manifest file
-
-Purpose: App registration and discovery
-
-Located at `/.well-known/farcaster.json`, this file contains your app's basic information for Base App's directory.
-
-
-Mini Apps require a complete manifest. Read the [manifest requirements](/mini-apps/features/manifest#example-manifest).
-
-
-#### Embed metadata
-
-Purpose: Embed generation when shared
-
-Located in your HTML `` tags, this metadata creates the rich embeds when users share your app.
-
-```html index.html
-
-```
-
-This controls how your embeds appear in social feeds.
-
-### Best Practices for Metadata
-
-- Image optimization: Use 3:2 aspect ratio
-- Clear value proposition in button and text
-- Visual consistency with product UI
-- Fast loading for metadata endpoints
-- Validate across platforms pre‑launch
-
-## Sharing
-
-### Adding Share Functionality
-
-Prompt users to share during key accomplishment moments using MiniKit’s compose hook.
-
-```ts ComposeCastButton.tsx
-import { useComposeCast } from '@coinbase/onchainkit/minikit';
-
-export default function ComposeCastButton() {
- const { composeCast } = useComposeCast();
-
- const handleCompose = () => {
- composeCast({
- text: 'Just minted an awesome NFT using @coinbase OnchainKit! ',
- });
- };
-
- const handleComposeWithEmbed = () => {
- composeCast({
- text: 'Check out this amazing Mini App!',
- embeds: ['https://your-mini-app-url.com'],
- });
- };
-
- return (
-
-
-
-
- );
-}
-```
-
-
-Strategic sharing moments:
-
-- After completing a quiz
-- After minting an NFT
-- After beating a challenge
-- After reaching a milestone
-
-
-### From the Base App UI
-
-Users can also share directly from your app's detail view in the Base app through the built‑in share functionality.
-
-
-
-
-
-## Embed Types
-
-### Static embeds
-
-Use a single, unchanging image and text for all shares. Best for consistency and speed.
-
-### Dynamic embeds
-
-Generate metadata per user or event for personalization and FOMO. Ensure fast response (< 5s) and caching strategy.
-
-## Implementation
-
-### With MiniKit (Next.js)
-
-```ts layout.tsx
-export async function generateMetadata(): Promise {
- const URL = process.env.NEXT_PUBLIC_URL;
- return {
- title: process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME,
- description: "Your Mini App description here",
- other: {
- "fc:frame": JSON.stringify({
- version: "next",
- imageUrl: process.env.NEXT_PUBLIC_APP_HERO_IMAGE,
- button: {
- title: `Launch ${process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME}`,
- action: {
- type: "launch_frame",
- name: process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME,
- url: URL,
- splashImageUrl: process.env.NEXT_PUBLIC_SPLASH_IMAGE,
- splashBackgroundColor: process.env.NEXT_PUBLIC_SPLASH_BACKGROUND_COLOR,
- },
- },
- }),
- },
- };
-}
-```
-
-### Without MiniKit
-
-```html index.html
-
-```
-
-## Debugging
-
-### Tools
-
-
- Test how your app will appear in the Base app by validating your manifest, embeds, and account association
-
-
-### Common issues
-
-
-
-Check image dimensions, required `fc:frame`, JSON validity, and URL accessibility.
-
-
-Review cache headers, repost to refresh, wait ~10–15 minutes for caches.
-
-
-Optimize image generation, pre‑generate, use serverless, compress assets.
-
-
-
-
-Set `Cache-Control` carefully: long enough for performance (300–600s), short enough for quick updates.
-
-
-## Next Steps
-
-
-
-
-
-
-
-
-Continue with:
-
-- [Search and Discovery](/mini-apps/troubleshooting/how-search-works)
-- [Manifest](/mini-apps/core-concepts/manifest)
-
-
-