Implementing Link Previews with Cloudflare Pages & Functions
Virtually all modern messaging and social media platforms display previews for links that are shared by their users. This makes messages, posts, and media that contain links more engaging. Today, implementing link previews is an important step that developers must not forget when building their web presence.
If you’re a developer using Cloudflare Pages to host and deploy your website, this guide will cover step-by-step instructions on how to implement link previews for your website!
Background
How do link previews work?
Most major platforms today will show link previews based on <meta>
tags in the HTML served by the website. These tags are what allow websites to give search engines and media platforms information about the site for things like SEO, and in our case, link previews.
One of the most popular standards for these previews is the Open Graph protocol, which we will be using in this guide. Here’s an example of what these look like for one of our artist’s profiles on Formfunction:
Cloudflare Pages, SPAs, and Client-side Rendering
Traditionally, many websites served HTML from a custom web server where most, if not all of the markup required to render a page was returned from the server (broadly referred to as “server-side rendering” or SSR). This gave developers the flexibility to tweak the HTML based on the content being served and do things like inject <meta>
tags into the HTML before sending it down to the client.
While SSR is still a technique that is commonly used today, the rise in popularity of single-page applications (SPAs), and especially those that utilize client-side rendering, has led to many websites foregoing hosting the frontend through their own servers in favor of using hosting solutions like Cloudflare Pages. By default, these setups will often serve the same static HTML which will load a JavaScript bundle that handles rendering of the site’s content.
This presents some challenges for developers using client-side rendering frameworks with Cloudflare Pages when it comes to injecting <meta>
tags into the HTML as the HTML that is served is often not customizable on a per-request basis.
Thankfully, this is made possible with a bit of code and Cloudflare Functions as you’ll see in the rest of this guide.
So let’s dive right into it!
Guide
As alluded to above, our high-level goal will be to write some code using Cloudflare Functions that allows us to intercept the client’s request, fetch the HTML, do some validation, and (if necessary) inject <meta>
tags into the HTML before sending it down in the response.
💡 Disclaimer: At the time of writing, Cloudflare Functions is still in Beta. Please be sure to consult the documentation if you run into any issues.
Step 1: Set up Functions
We’ll get started by getting a basic Functions setup up and running.
- Create a
/functions
directory at the root of your Cloudflare Pages project. - For the purpose of this guide, we want our Function to match all URLs so we’ll create a file called
[[index]].ts
in the/functions
directory we just created. (Please refer to the documentation for more advanced routing) - Paste the following code into
[[index]].ts
- NOTE: you can install
@cloudflare/workers-types
as a dev dependency to get relevant types installed in your dev environment
That’s it! That’s all that it takes to get a Cloudflare Function set up and ready to deploy. However, our code is hardly in a production-ready state so it’d be undesirable to push and deploy this code. Luckily, Cloudflare’s Wrangler tool allows us to test this locally:
# Install Wrangler
npm install wrangler@beta# Run Functions locally using Wrangler
npx wrangler pages dev -- npx react-scripts start
Once the command is done running, you should be able to access your local frontend by navigating to https://localhost:8788
. Your terminal (running Wrangler) should output the request URL that is being requested by the client once you load the page!
Step 2: Implement a custom ElementHandler
The next step is to get the <meta>
tag injection working. To accomplish this, we’ll be using the HTMLRewriter
API that Cloudflare provides as part of the Functions runtime.
All we need to do to use this API is to implement an ElementHandler
which has the following interface:
In our case, we’ll write a handler that injects <meta>
tags for us by attaching to the <head>
element in our HTML. To see how this works in practice, try modifying [[index]].ts
with the following code:
Now, once you save the file and go to view-source:http://localhost:8788
you should be able to see the tag we injected under the <head>
element in the page source!
Step 3: Set up API to fetch preview data
Now that we have a simple setup for intercepting requests and injecting meta tags, we need a way to actually fetch the preview data to populate our tags with live content.
This part is application-specific and will be different for everyone but a simple way to get started is to set up a REST endpoint on your backend to fetch the data you need. As a reminder, we’ll be injecting the following properties of the Open Graph specification:
- URL
- Type (will be
”website”
in this case) - Title
- Description
- Image
Depending on which properties you want to customize, your data requirements may differ. At a minimum, we recommend fetching the image
and some sort of name/identifier to customize the title
.
For example, suppose you’re fetching data for a user, you might set up a GET
endpoint for /users/:userId
which returns
{
"image": "<asset URL>",
"username": "<username>"
}
Step 4: Wire everything up
Now we can wire everything up end-to-end. With our newly defined GET
endpoint we can modify our code to look like the following:
Let’s break this down:
- We fetch the data from our backend by making a
GET
request to<API_URL>
- We feed the data in to
MetaTagInjector
which now acceptsMetaTagInjectorInput
(in this case, the data has the same shape as what is returned from our example API) MetaTagInjector
populates the<meta>
tags with the data, and appends<meta>
tags to the<head>
element- We return the transformed HTML to the client
Give it a try on your local setup!
Step 5: Productionize
Congrats! You’ve just built a basic Cloudflare Function that injects meta tags to enable link previews for your SPA! 🥳
However, the example code is still fairly rough and may need some additional steps to be considered production-ready. Although we won’t walk through all of these steps in this guide, here are some things you may want to address before deploying your code:
- Route matching — in our example, we’re calling the API for every request which can be expensive. For production use, you’ll likely want to conditionally call the appropriate API based on the request URL (in
request.url
). For example, we use a Regex pattern to see if a URL is requesting a user profile before trying to fetch the image and username. - Error handling & safe defaults — your API request might fail so it’s important that your Functions code can fallback to a set of safe default values.
- Cache the API data — the API data fetch can add significant time to your overall request roundtrip time. In most cases, the data being fetched will not be changing frequently so making use of caching with a reasonable TTL will drastically improve performance of this method. Try making use of Cloudflare’s KV binding to cache your fetched data between requests!
Step 6: Deploy
One of the best parts of Cloudflare Functions is that it doesn’t require any special deployment steps to get things working. Simply deploy your app like you normally would (i.e., git commit
and git push
) and watch the magic unfold!
💡 Tip: if you’re using a repository setup where your frontend app code does not live at the root of your repository (e.g., a monorepo setup), you can add a symlink to your nested functions
directory at your repository root as part of your deployment steps to get things up and running.
Gotchas
Here are some gotchas that you may want to be aware of when using Cloudflare Functions:
- Beta — as noted above, Cloudflare Functions are still in beta so please keep that in mind when choosing this setup for your production use-case. Be sure to consult the documentation or the Cloudflare Discord if you need assistance!
- Invocation limit — Cloudflare Functions currently has a 400,000 daily invocation limit that can only be changed via requesting an increase. Once a Function reaches this limit, it will simply stop running and fallback to serving static assets. See the docs for more info.
- Computation limit — there is a (unstated?) computation limit of 50ms and any Function invocation that exceeds the 50ms of processing time will result in an error. It is worth noting that async operations like I/O or network requests do not count towards this processing time limit! In our case, we ran into this due to a bug in one of our Regexes that caused our processing time to exceed the 50ms limit.
Final Thoughts
And that’s a wrap! Congrats on building a simple meta tag injection system using Cloudflare Functions on your Cloudflare Pages SPA. 🙌
If you enjoyed this article, please support us by giving us a 👏, leaving a comment with your thoughts, or by simply sharing this article with your network. You can also reach us on Twitter (@Formfunction) or by joining our Discord where one of our team members would be happy to meet and chat with you.
Stay tuned for more posts like these from the Formfunction engineering team. And if you’re also passionate about building a better world for creators — from building full-scale products from the ground up to designing subtle details like beautiful link previews — come join us!