Twitter Cards for onchain SVG NFTs
Tags: web3, nextjs, javascript, programming
Published: 2023-08-18 16:49:06

I've had 2 projects recently where I wanted to encourage users to post a Tweet that would display an image for an onchain SVG NFT. Twitter doesn't provide an easy way to prompt users to Tweet an image unless you have your users authenticate their Twitter account. But OpenGraph meta tags on your page will append a Twitter Card with an image of the developer's choosing to any tweet that links to your site.

This is a superior alternative to telling users to screenshot the page, but if the image content you want shared in-feed is an SVG, you're gonna have a bad time.

Let's look at this Tweet that shares a link to [an onchain SVG NFT on OpenSea](https://opensea.io/assets/ethereum/0xf9bdddba8011262382182cdfa7c92327aff15279/46).


![Screenshot of a Tweet that does not display an image for the NFT being linked to](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zwg50sj5kues9jhed1qb.png)

No image is displayed here because the OpenGraph metatags on the linked URL include an SVG image as the content for the `twitter:image` metatag:

![Web page source code for a page that won't render an image attached to a tweet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvyy7y0kulam70fc5coa.png)

In order to get an image to display in-feed, we need to provide a URL to a supported image type. I don't know what the supported types are, but I do know that SVG is not supported, while JPG and PNG are. So if we only have an SVG image, and want users to be able to share that image on Twitter without screenshotting and without signing into their Twitter account, we need to replace the SVG image referenced in the metatag with a link to a PNG or JPG version of the image.

I don't particularly like managing servers and certainly don't want to create a whole SVG -> PNG asset pipeline for this functionality. And things are further complicated if you're using NextJS or similar frameworks - the Twitter bots won't execute client-side JS, so the metatags with the supported image URL need to be rendered server-side.

Let's see how we can solve this using NextJS's `getServerSideProps` and an NFT indexing service [SimpleHash](https://simplehash.com/) that provides great coverage and handles caching and [serving cached images specifically for OpenGraph](https://docs.simplehash.com/reference/overview#media-previews) usage in the NFT data they index and serve over an API.

## getServerSideProps

This isn't a NextJS tutorial, so if you're unfamiliar with NextJS SSR and `getServerSideProps` you should review the NextJS docs. Simply put, `getServerSideProps` allows us to write some code that executes on the server, and passes the return value of that code as props to a NextJS page component served at a specific URL, allowing dynamic code to be rendered by the server rather than the client.

This is useful to us here because we need our server to render the metatags in order for Twitter to know what to display in-feed when a Tweet links to your site. If you tried to run the same code in a React component's `render` method, Twitter's bots will not index the result.

We'll walk through a simplified example of a NextJS page component that uses `getServerSideProps` to render dynamic metatags.

Let's start with a simple skeleton for a page that represents a single NFT displayed at a dynamic route. We'll set it up to expect an `imageUrl` prop for the NFT:

```typescript
// app/pages/[nftContractAddress]/[nftTokenId].tsx

import type { GetServerSideProps } from "next";
import Head from "next/head";

interface MetaTagData {
  imageUrl: string;
}

export default function NFTDetailPage({ imageUrl }: MetaTagData) {

  return(
    <>
      <Head>
        {/* our metatags will go here */}
      </Head>
      {/* rest of markup */}
    </>
  );
}

```

With that skeleton in place, we can next add to the same file our `getServerSideProps` implementation. This code will run on the server, and needs to return an object that satisfies the page's typed props. For now we'll use a placeholder image:

```typescript
export const getServerSideProps: GetServerSideProps<MetaTagData> = async (context) => {
  return {
    props: {
      imageUrl: "https://placekitten.com/600/400",
    },
  };
};
```

## Image Fetching

With our skeleton in place, we can now replace our placeholder with an image that is supported by Twitter for in-feed image display that represents the NFT at the requested dynamic URL.

SimpleHash is great for this - their API returns a url to a JPG they host that Twitter is happy to display and which is optimized specifically for this OpenGraph purpose. You'll need an API key (free up to 20k monthly requests).

We pull the contract address and token ID for the NFT off of  the URL, call the SimpleHash API, and return an object that includes the optimized image url from the API response:

```typescript
// app/pages/[nftContractAddress]/[nftTokenId].tsx

export const getServerSideProps: GetServerSideProps<MetaTagData> = async (
  context
) => {
  const { nftContractAddress, nftTokenId } = context.params;
  const data = await fetch(
 `https://api.simplehash.com/api/v0/nfts/ethereum/${nftContractAddress}/${nftTokenId}`,
    {
      headers: {
        "X-API-KEY": process.env.NEXT_PUBLIC_SH_KEY!,
      },
    }
  )
    .then((resp) => resp.json())
    .catch((e) => {
      console.error(e);
    });


  return {
    props: {
      imageUrl: data?.previews?.image_opengraph_url || "",
    },
  };
};
```
This function will execute on the server, and pass the return value to our page component as props. Next, we'll use those values to display metatags that tell Twitter to render an image of our NFT in-feed.


## Metatags

If you're not familiar with OpenGraph metatags, you should review the documentation to know exactly what tags you need for display and what options are available to you. For now we will hardcode some values and show how to use the `imageUrl` we gathered.

Back in our page component markup, let's add some metatags:

```typescript

// app/pages/[nftContractAddress]/[nftTokenId].tsx

import type { GetServerSideProps } from "next";
import Head from "next/head";

interface MetaTagData {
  imageUrl: string;
}

export default function NFTDetailPage({ imageUrl }: MetaTagData) {

  return(
    <>
      <Head>
        <meta property="twitter:card" content="summary_large_image" />
        <meta property="twitter:url" content="https://mysite.com" />
        <meta property="twitter:title" content="NFT" />
        <meta property="twitter:description" content="This is an NFT" />
        {/* <!-- Dynamic Image Here             vvvvvv --> */}
        <meta property="twitter:image" content={imageUrl} />
      </Head>
      {/* rest of markup */}
    </>
  );
}

// `getServerSideProps` implementation...
```

## Caching

Depending on your project it might also be a good idea to cache the API response and serve the cached data rather than querying the SimpleHash API for every request. This will help keep your API requests under limits and serve your page faster once data is cached.

I found Vercel's Redis offering and `kv` package to be super simple to setup. 

We need to create a client and check our cache for appropriate data, returning the cached JSON if we find a hit. Otherwise, we request the API and write to our cache for subsequent requests:

```typescript
  const store = createClient({
    url: process.env.KV_REST_API_URL!,
    token: process.env.KV_REST_API_TOKEN!,
  });

  const props = await store.hgetall(`${nftContractAddress}-${nftTokenId}`);

  if (props) {
    return {
      props: props as unknown as MetaTagData,
    };
  }

// .... API request

  await store.hset(`${nftContractAddress}-${nftTokenId}`, {
    imageUrl: data?.previews?.image_opengraph_url || "",
  });

```

## Shipit

We're done! This blog post took a lot longer to write than writing the code itself. It's not a show-stopping feature, but if you want your onchain art to show on Twitter and other social networks, this feels like the best way to accomplish that goal without setting up your own image conversion pipeline.

Here's what a tweet linking to an NFT page would like look, compared to the tweet linking to OpenSea we saw earlier:


![Screenshot of a Tweet that does display an image for the NFT being linked to](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/76j0k9zvh1apqwfly6qa.png)

For debugging purposes, [metatags.io](metatags.io) is a great site that will crawl your metatags and show you how your link will appear on various social platforms.

I like onchain NFTs and it's frustrating that it takes a little extra work to make onchain art display on Twitter. Hopefully this helps you helps me see more onchain art on Twitter.

You can follow me there [@sammybauch](https://twitter.com/sammybauch), and if you also like onchain NFTs, check out my onchain projects [Blitpop](https://v1.sudoswap.xyz/#/browse/buy/0xF9BddDBa8011262382182CdFA7c92327afF15279) and [Blockclock](https://blockclock.0xessential.com/) and keep an eye out for some new collections coming soon!