Open Graph Meta Tags through Lambda@Edge injection

The problem


With single-page applications becoming more common we are left to combat different types of issues with the usability of our sites and web applications. Search engine optimization and social previews of sites generally rely on machine readable pages without running any code client side.

 

Facebook sharing preview from Kickets.com before og tag injection

Since SPA sites only contain a bare container for running the actual application metadata necessary for SEO and OG tags is missing from all but the root of the application. Solutions such as Angular Universal and Next.js for React have emerged to solve this (and some other things) but at least for Angular using Universal doesn’t translate that well into an all serverless environment.

Rather than embark on a possible perilous journey of static site generation or server-side rendering we thought why couldn’t we just inject correct metadata into the bare index.html if the requester is a robot user and the requested route meets certain conditions?

The solution

  1. Most of the applications we develop and maintain are hosted on Amazon Web Services. S3 buckets are used to host static assets to make our single-page applications work, and APIGateway or AppSync go in tandem with Lambda to provide a backend for the application. Finally CloudFront ties this all together to provide the application for the end user.

To be able to inject code into the index.html CloudFront serves from S3 we can use an
origin response event. The basic idea is simple:

  1. If the user-agent does not match with any well-known robots, return the response from S3

  2. If the request is for a static asset like .js or .css file, return the response from S3

  3. If the requested route doesn’t match our list of routes to handle metadata for, return the response from S3

  4. Finally an interesting route is found, handle route specific processing and return injected index.html

As we are running a SPA site every route through CloudFront returns basically the same root index.html and Angular handles routing and rendering of the correct page when running on the client. That way we always deal with the same page, only the injected SEO and OG tags differ based on the requested route.

When handling a specific route we need to do the following things:

  1. Get the index.html from S3 again – even though we are running an origin response function
    we have no handle on the body and have to get the file in order to inject data

    Note: AWS SDK is not available in the Lambda@Edge runtime and due to the latency sensitive nature of running functions for each asset request we think it’s best to do a more low-level fetch for the file instead of using a bundled SDK

  2. Do a query against the API to get all of the data we want to inject into the page before returning

  3. Inject the data into the page

    Note: If injecting using string replacement be sure to include all the necessary tags in the root index.html even if some of the fields might be empty by default

  4. Return the response to the viewer

Injecting the tags might look like this:

Notes:

  • og:description shouldn’t be very long, here we cut our description at the first whitespace after specific length

  • og:image has some specific guidelines that you should take into account

  • Our CloudFrontResponseEvent request uri does not contain our custom hostname so we insert that so that og:url matches what the robot actually requested

Finally we use the serverless.com framework for deployment of our Lambda@Edge. To handle configuration of CloudFront and its behaviors automatically we use the serverless-lambda-edge-pre-existing-cloudfront -plugin that helps us define which behaviors in which CloudFront distributions we want to configure with lambda@edge functions by event type:

The functions and their deployment is then configured:

After deployment of our serverless project we can re-visit our page in the Facebook sharing debugger.

Facebook sharing preview from Kickets.com after og tag injection

This method works for all SPA sites and is a cost-effective way of setting up a proper social media sharing preview experience for your application running in AWS.

What about the price? Since most of our executions handle actual users or assets we don’t want to inject into we don’t actually get that much duration in our executions. In the month of May 2023 spread evenly through ~230k CloudFront requests the average Lambda@Edge duration was 8 milliseconds and in total amounted in about $1 of costs. This can be further optimized through caching to prevent most of the CloudFront requests from hitting our Lambda@Edge function at all.