Rich Social Sharing with single page applications hosted on S3 and delivered via CloudFront

Guest Thorsten Höger – 06 Jul 2018

You undoubtedly heard about single page applications (SPA) written with frameworks like Angular or React. One of the benefits of this approach is the possibility to host the static files (HTML, js, CSS, etc.) on a simple storage solution like S3 and put a CDN like CloudFront in front of it (learn more about using S3 for static web hosting).

Rich social sharing means that if you include an URL in a text that you share, the social network (e.g. on Facebook) fetches additional data like an image and the title of the URL and displays them instead of the raw URL. This blog post contains the following meta tags to allow a rich social sharing experience:

<meta property="og:title" content="Rich Social Sharing with single page applications hosted on S3 and delivered via CloudFront">
<meta property="og:description" content="You undoubtedly heard about [...]">
<meta property="og:image" content="https://cloudonaut.io/images/2018/07/facebook.png">

If you want to share your SPA on Facebook or Twitter, you will face a problem. Your meta tags are dynamically filled with JavaScript. So the initial HTML looks like this:

<meta property="og:title" content="{{pageTitle}}" />
<meta property="og:description" content="{{pageDescription}}" />
<meta property="og:image" content="{{pageImage}}" />

Facebook (and other social networks) will present the placeholders in the preview. The rich experience is broken.

Facebook

The problem here is that social networks just read the HTML source but do not execute the JavaScript. To make this work, you have to

  1. Render the pages as static HTML files where placeholders are replaced with the actual values.
  2. When a social bot is crawling your page, deliver the static HTML file instead of the SPA

For pre-rendering, you can use prerender.io (or others) which you can hook into your web server like nginx or Apache. The caveat here is that with S3 you do not have a web server.

Lambda@Edge for the rescue

Fortunately, Lambda functions can be hooked into web page delivered via CloudFront. When a social bot is requesting your SPA, the request should be forwarded to prerender.io instead of your S3 bucket. The forwarding can be achieved with a Lambda@Edge function that inspects the User-Agent header.

The Lambda function checks if the user agent is one of the known social bots and decided whether to serve the original website or the pre-rendered version by overriding the configuration of the origin request.

Lambda sourceGitHub
exports.handler = (event, context, callback) => {
'use strict';
const request = event.Records[0].cf.request;
const userAgent = request.headers['user-agent'][0].value;

let prerender = 0;
// user agent is known social bot
if (userAgent.match(/baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator/i)) {
prerender = 1;
}
// rendered page is requested by special query string
if (request.querystring.match(/_escaped_fragment_/)) {
prerender = 1;
}
// request is coming from prerender.io, so serve original page
if (userAgent.match(/Prerender/)) {
prerender = 0;
}
// request targets non-html files
if (request.uri.match(/\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff|svg|eot)/i)) {
prerender = 0;
}

if (prerender === 1) {
request.origin = {
custom: {
protocol: 'http',
domainName: 'service.prerender.io',
port: 80,
path: '/https://www.example.de',
sslProtocols: ['TLSv1', 'TLSv1.1'],
readTimeout: 30,
keepaliveTimeout: 30,
customHeaders: {
'x-prerender-token': [
{
"key": "X-Prerender-Token",
"value": "yourPrerenderIOToken"
}
]
}
}
};
}
callback(null, request);
};

CloudFront configuration

To configure CloudFront to call the Lambda@Edge function:

  1. Select your distribution and click on the Edit button
    CloudFront step 1
  2. Whitelist the User-Agent header and configure the Lambda@Edge function for the origin-Request event type
    CloudFront step 2

After updating the CloudFront distribution, which could take up to 40 minutes, you can share your web page using social media.

Summary

Enable a rich social sharing experience for SPAs hosted on CloudFront + S3 with Lambda@Edge. Twitter offers a page to test the rendering of a page under https://cards-dev.twitter.com/validator

Thorsten Höger Guest

I’m the CEO and cloud consultant at Taimos, where I’m advising customers on how to use AWS. Being a developer, I focus on improving development processes and automating everything to build efficient deployment pipelines for customers of all sizes.

Here are the contact options for feedback and questions.