-
Notifications
You must be signed in to change notification settings - Fork 287
Docs/dynamic tech guide #580
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3203aed
9db8bc9
8e0de22
5433649
f381926
469b221
5539d5c
4660137
5d530bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
|
|
||
| <Panel> | ||
| <Frame caption="How metadata transforms into embeds"> | ||
| <img src="/images/minikit/Diagram.png" alt="Diagram showing the flow from mini app URL to metadata reading to image generation and final embed rendering in the Base app" /> | ||
| </Frame> | ||
| 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. | ||
| </Panel> | ||
|
|
||
| <Note> | ||
| This guide uses Minikit but the principles apply to any framework with server-side rendering. | ||
| </Note> | ||
|
|
||
| ## 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. | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's just turn this into a step:
npm install @vercel/og
|
||
| <Steps> | ||
| <Step title="Install the required package"> | ||
| 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 | ||
| ``` | ||
| </Step> | ||
| <Step title="Create the image generation API endpoint"> | ||
| 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( | ||
| ( | ||
| <div | ||
| style={{ | ||
| backgroundColor: 'black', | ||
| backgroundSize: '150px 150px', | ||
| height: '100%', | ||
| width: '100%', | ||
| display: 'flex', | ||
| color: 'white', | ||
| textAlign: 'center', | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| flexDirection: 'column', | ||
| flexWrap: 'nowrap', | ||
| }} | ||
| > | ||
| Hello {username} | ||
| </div> | ||
| ), | ||
| { | ||
| width: 1200, | ||
| height: 630, | ||
| } | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| This endpoint generates a unique image for each username: `/api/og/alice`, `/api/og/bob`, etc. | ||
|
|
||
| <Warning> | ||
| `<div>` elements must have `display: "flex"` or `display: "none"`. If you see a 500 error when accessing `/share/[username]`, check your ImageResponse JSX structure. | ||
| </Warning> | ||
| </Step> | ||
|
|
||
| <Step title="Create shareable page with dynamic metadata"> | ||
| 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"; | ||
soheimam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| import { Metadata } from "next"; | ||
|
|
||
| export async function generateMetadata( | ||
| { params }: { params: Promise<{ username: string }> } | ||
| ): Promise<Metadata> { | ||
| 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 ( | ||
| <div> | ||
| <h1>Share Page - {username}</h1> | ||
| </div> | ||
| ); | ||
| } | ||
| ``` | ||
| When someone visits `/share/alice`, the metadata points to `/api/og/alice` for the embed image. | ||
| </Step> | ||
|
|
||
| <Step title="Add share button with composeCast"> | ||
| 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 ( | ||
| <div> | ||
| <button onClick={handleShareApp}> | ||
| Share Mini App | ||
| </button> | ||
| </div> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| 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`. | ||
| </Step> | ||
|
|
||
| <Step title="Test the flow"> | ||
| 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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's replace "Verify the complete sharing flow works" with this paragraph. |
||
| </Step> | ||
| </Steps> | ||
|
|
||
| ## Related Concepts | ||
|
|
||
| <CardGroup cols={1}> | ||
| <Card title="Troubleshooting" href="/mini-apps/troubleshooting/how-search-works"> | ||
| Troubleshooting tips for embeds not displaying. | ||
| </Card> | ||
| </CardGroup> | ||
Uh oh!
There was an error while loading. Please reload this page.