How to build a Mini App Feed on Farcaster and The Base App

How to create a Mini App Feed on Farcaster and Base App and render it in your Mini App or full blown React Apps (NextJS too).

We will develop a MiniApp Rendering System as component that automatically detects Farcaster Mini Apps within feed content and provides seamless integration for opening them - yes both in a mini app and on the web (outside mini apps). See how below the cast there's a "Open App" Button that also shows the domain of the mini app?

You can clone our mini app sample that provides a mini app rendering a feed of mini apps here on Github or run the following command to set it up on your computer.

bunx degit https://github.com/ZKAI-Network/embed-sdk/examples/minikit-nextjs-miniappfeed embed-miniapp

Now cd embed-miniapp, install dependencies bun install and run the mini app locally bun dev. Make sure you have set the environment variables.

By filling in environment variables and setting up the project you should now see a feed of Farcaster posts on your screen.

How to do it yourself, developing a React/NextJS App with a Mini App Feed

Use the embed SDK!

The quick TLDR is on the API side, where we are going to call the embed API's to give us a full feed, we add filters to only return mini app posts! That's it.

// Call the Embed AI SDK for feed data
const embedClient = getEmbedClient();
const feedData = await embedClient.feed.byUserId(fid, feedId || undefined, {
  top_k: 10,
  filters: {
    // ensures we only get mini apps returned for our feed.
  	publication_types: ["miniapp"]
	}
});

See the publication_types filter? that's the magic behind getting a mini app only feed. The feedId we use is a template feedId provided by embed as a great start for a feed. You'll see the component passes feedId="feed_626".

The full implementation of that API endpoint at app/api/feed/route.ts is provided in the Github Example here.

When do we render what?

In case there are more embeds then a mini app we can see images, videos and more. We shall handle all these embeds properly so the user sees a nice feed of content.

Regarding Mini App Rendering we need to fetch the Mini App preview components which consist of primarily the button title, title/name of the app and most importantly the preview image! You can see such a sample from the Cura Mini App right here as we render it's preview image (here a leaderboard), the title "Cura", the link "cura.network" and the button title "/girlypop on Cura".

Now there are multiple ways to do this. Do we want to have the cast even present? do we just want recommendations of apps only? Do we want to render the engagement buttons below the cast or all the way below the mini app embed? Here's how the Farcaster app does it.

The cast we tested: https://farcaster.xyz/kunolawuyi/0xeac1e33a

Though how do we even get the metadata we need to render this preview? Since each url has a fc:frame or fc:miniapp metatag we scrape that!

Since we may run into errors fetching different domains from our webapp we create an API route that we call to return the metadata. A cool side effect of that is NextJS caching can be used as well as any caching layer like Redis once your app has high volumes of traffic.

To do so we'll create app/api/miniapp/route.ts with an implementation matching the following logic:

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const url = searchParams.get('url');
  
	// fetch the requested URL
	const response = await fetch(url, {
    method: 'GET',
    headers: {
      'User-Agent': 'Mozilla/5.0 (compatible; MiniAppChecker/1.0)'
		},
  });
  
	// get the full html and extract if it's a mini app + parse out the data
	const html = await response.text();
  const miniAppMeta = extractMiniAppMeta(html);
  
	return NextResponse.json({
    isMiniApp: miniAppMeta !== null,
    metadata: miniAppMeta
  }, {
    status: 200,
  });
}

The full implementation is provided in the Github Example here.

Now that we have a way to filter for Mini Apps and get their metadata, we want to apply that to our feed.

Rendering a Feed in React for our Base & Farcaster Mini Apps

To render a feed of mini apps in our app we will use the @embed-ai/react package which provides the Base components, which we will adjust to add our Mini App preview.

Getting started we add a FeedGrid to render each FeedItem. The respective component and type can be imported from the @embed-ai/react package:

import { FeedGrid, type FeedItem } from "@embed-ai/react/feed";

Our FeedContainer component in app/components/FeedContainer.tsx wraps FeedGrid to provide functionality of what we want to happen if someone presses like, reply or share.

Most importantly since we will render our own Custom preview we need a CustomFeedCard. This is the component that will render each Post (each FeedItem).

How to integrate is illustrated in the following code snippet:

  return (
    <div className="w-full max-w-full overflow-hidden feed-container">
      <FeedGrid
        title={title}
        isLoading={isLoading}
        error={error}
        isFetchingNextPage={isFetchingNextPage}
        hasNextPage={hasNextPage}
        onRefresh={refetch}
        isRefreshing={isRefreshing}
        loaderRef={ref}
        isEmpty={isEmpty}
      >
      {data &&
        data.map((item) => (
          <CustomFeedCard
            key={item.item_id}
            item={item}
            onShare={() => handleShare(item)}
            onReply={() => handleReply(item)}
            onViewProfile={() => handleViewProfile(item)}
            onTip={() => handleTip(item)}
          />
        ))}
      </FeedGrid>
    </div>
  );

Notice how FeedGrid wraps around our CustomFeedCard. The full implementation is provided in the Github Example. The Mini App specific code happens in our CustomFeedCard since this is where we process rendering each post that we got recommended by the embed API.

The CustomFeedCard filters out mini apps from the main FeedCard to avoid duplication and render's them in our MiniAppEmbedRenderer instead of the default FeedCard.

/**
 * Custom FeedCard wrapper that processes Mini App embeds
 * This component filters out mini apps from the main FeedCard to avoid duplication
 */
export function CustomFeedCard({ item, ...props }: CustomFeedCardProps) {
  const [miniAppUrls, setMiniAppUrls] = useState<Set<string>>(new Set());

  const embedUrls = item.metadata.embed_items
    ?.filter(embed => typeof embed === 'string' && (embed.startsWith('http://') || embed.startsWith('https://')))
    || [];

  useEffect(() => {
    if (embedUrls.length === 0) return;
    
    batchCheckMiniApps(embedUrls as string[])
      .then(results => {
        const miniApps = new Set<string>();
        results.forEach((isMiniApp, url) => {
          if (isMiniApp) miniApps.add(url);
        });
        setMiniAppUrls(miniApps);
      });
  }, [embedUrls.join(',')]);

  // Create modified item with mini app URLs filtered out of embed_items
  const modifiedItem = {
    ...item,
    metadata: {
      ...item.metadata,
      embed_items: item.metadata.embed_items?.filter(embed => 
        typeof embed !== 'string' || !miniAppUrls.has(embed)
      )
    }
  };

  return (
    <div className="w-full max-w-full overflow-hidden" style={{ maxWidth: '100%', boxSizing: 'border-box' }}>
      {/* Render FeedCard with mini app URLs filtered out */}
      <div 
        className="..."
        style={{ maxWidth: '100%', boxSizing: 'border-box', overflow: 'hidden' }}
      >
        <FeedCard item={modifiedItem} {...props} />
      </div>
      
      {/* Render Mini App embeds separately */}
      {embedUrls.length > 0 && (
        <div className="miniapp-embeds-container mt-3">
          {embedUrls.map((embed, index) => (
            <div key={`embed-${index}`} className="w-full max-w-full mb-2">
              <MiniAppEmbedRenderer embed={embed} />
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

The Mini App Renderer

To render each mini app preview into our feed we will check for the Farcaster Mini App Metadata via the API route we created and use that to render.

The Mini App Renderer manages the state transition between the actual MiniAppEmbed and a loading screen skeleton as the following pseudocode illustrates:

  function MiniAppEmbedRenderer({ embed }) {
    const [isMiniAppUrl, setIsMiniAppUrl] = useState(null)

    useEffect(() => {
      checkIfUrlIsMiniApp(embed)
        .then(setIsMiniAppUrl)
    }, [embed])

    if (isMiniAppUrl === null) return LoadingSpinner()
    if (!isMiniAppUrl) return null

    return <MiniAppEmbed embed={embed} />
  }

The MiniAppEmbedRenderer is the component actually rendering the Mini App preview as it gets the mini app data from our API and then renders the Image, Title and Button.

// MiniAppEmbedRenderer Component Pseudocode

  function MiniAppEmbed({ embed }) {
    const { context } = useMiniKit()
    const [metadata, setMetadata] = useState(null)
    const [loading, setLoading] = useState(true)
    const [error, setError] = useState(false)

    const domain = extractDomain(embed)
    const isInMiniApp = Boolean(context?.client)

    useEffect(() => {
      fetchMiniAppMetadata(embed)
        .then(setMetadata)
        .catch(() => setError(true))
        .finally(() => setLoading(false))
    }, [embed])

    function handleOpenApp() {
      const url = metadata?.button.action.url || embed

      if (isInMiniApp) {
        sdk.actions.openMiniApp({ url })
      } else {
        openFarcasterDeeplink(url)
      }
    }

    if (loading) return LoadingSkeleton()
    if (error || !metadata) return FallbackCard(domain, handleOpenApp)

    return (
      <div className="card">
        <img src={metadata.imageUrl} aspectRatio="3:2" />
        <div className="content">
          <h3>{metadata.button.action.name || domain}</h3>
          <p>{domain}</p>
          <Button onClick={handleOpenApp}>
            {metadata.button.title}
          </Button>
        </div>
      </div>
    )
  }

The full implementation is provided in the Github Example here.

What do we do on Button click? We don't know if our webapp is opened in a mini app or not.

Now before running the app make sure you have setup your environment variables.

We have now developed a NextJS app that renders a feed of mini apps!


For a ready made sample you can clone our mini app sample by running

bunx degit https://github.com/ZKAI-Network/embed-sdk/examples/minikit-nextjs-miniappfeed/ embed-miniapp

Now cd embed-miniapp, install dependencies bun install and run the mini app locally bun dev. Make sure you have set the environment variables.

Congratulations! You have now setup your own app with a custom Farcaster Feed or jump started your project by cloning the sample.