This is the SpeedWorkers viewer-request Lambda@Edge:
// SpeedWorkers viewer-request Lambda@Edge
//
// Runs on every incoming request:
// - classifies the request as bot or user (x-sw-request-type header, used to
// partition the CloudFront cache between bots and users)
// - saves the original host / user-agent / if-modified-since headers so the
// origin-request lambda can rebuild the original url for SpeedWorkers
//
// Requirements: deployed in us-east-1, timeout 1 second (the code only
// manipulates headers; raise the timeout if you add your own verification
// HTTP call below).
// ------------------------------ BOT PATTERN ---------------------------------
// IMPORTANT: while the SpeedWorkers integration is NOT finished, keep ONLY the
// test pattern "botify-bot-sw-" active. This makes the lambda route ONLY
// Botify's synthetic test bot to SpeedWorkers, so real search-engine / AI-bot
// traffic keeps hitting your website normally and cannot be broken by an
// incomplete integration.
//
// When you are ready to go to production:
// 1. Comment the TEST line below.
// 2. Uncomment the PRODUCTION line above it.
//
//
// PRODUCTION (uncomment when ready):
// const swBotPattern = "(googlebot\/|google-inspectiontool\/|googleother|googlebot-image\/|bingbot\/|yandexbot\/|yandexmobilebot\/|baiduspider\/|baiduspider\+|botify-bot-sw-|applebot\/|yeti\/|gptbot\/|chatgpt-user\/|oai-searchbot\/|oai-adsbot\/|amazonbot\/|anthropic-ai|claudebot\/|claude-web|claude-user\/|bytespider|ccbot\/|facebookbot\/|meta-externalagent\/|perplexitybot\/|perplexity-user\/|prerender|mistralai-user\/)";
// TEST ONLY — remove and switch to the PRODUCTION line above before going live:
const swBotPattern = "(botify-bot-sw-)";
// Don't touch this
const swIgnored = [
"/graphql",
"/css/",
"/fonts/",
"/images/",
"/js/",
"/scss/",
".bmp",
".css",
".csv",
".doc",
".docx",
".eot",
".gif",
".ico",
".ief",
".jpe",
".jpeg",
".jpg",
".js",
".json",
".less",
".map",
".m1v",
".mov",
".mp2",
".mp3",
".mp4",
".mpa",
".mpe",
".mpeg",
".mpg",
".otf",
".ott",
".ogv",
".pbm",
".pdf",
".pgm",
".png",
".ppm",
".pnm",
".ppt",
".pps",
".ps",
".qt",
".ras",
".rdf",
".rgb",
".rss",
".svg",
".swf",
".tiff",
".tif",
".tsv",
".ttf",
".txt",
".vcf",
".wav",
".webm",
".woff",
".woff2",
".xbm",
".xlm",
".xls",
".xml",
".xpdl",
".xpm",
".xwd",
]; // in lower case
const re = new RegExp(swBotPattern, "i");
export const handler = async (event) => {
const request = event.Records[0].cf.request;
// Never trust x-sw-* headers sent by the viewer, except the SpeedWorkers
// test-control headers (x-sw-options / x-sw-options-auth): the validation
// tests depend on them and SpeedWorkers authenticates them itself
for (const name of Object.keys(request.headers)) {
if (
name.startsWith("x-sw-") &&
name !== "x-sw-options" &&
name !== "x-sw-options-auth"
) {
delete request.headers[name];
}
}
// It's a user request unless proven otherwise
setRequestType(request, "user");
// SpeedWorkers only serves GET/HEAD page requests
if (request.method !== "GET" && request.method !== "HEAD") {
return request;
}
// Look for bots
if (
request.headers["user-agent"] === undefined ||
!re.test(request.headers["user-agent"][0].value)
) {
return request;
}
// Ignore resources that are not handled by SW
if (ignorePath(request.uri)) {
return request;
}
// If you need to verify bot requests with your own logic (e.g. an HTTP call
// to your bot management provider), plug it in here and `return request;`
// (classified as a user) when the verification fails.
// Add original host and user-agent in the headers to be able to rebuild the original url for SpeedWorkers and filter
if (request.headers.host !== undefined) {
request.headers["x-sw-host"] = [
{ key: "x-sw-host", value: request.headers.host[0].value },
];
}
if (request.headers["user-agent"] !== undefined) {
request.headers["x-sw-user-agent"] = [
{ key: "x-sw-user-agent", value: request.headers["user-agent"][0].value },
];
}
if (request.headers["if-modified-since"] !== undefined) {
request.headers["x-sw-if-modified-since"] = [
{
key: "x-sw-if-modified-since",
value: request.headers["if-modified-since"][0].value,
},
];
}
setRequestType(request, "bot-" + Date.now() + "-" + Math.random());
return request;
};
// Returns true if the url contains a path sw should ignore
function ignorePath(url) {
let l = url.toLowerCase();
for (let i = 0; i < swIgnored.length; i++) {
if (l.includes(swIgnored[i])) return true;
}
return false;
}
function setRequestType(request, type) {
request.headers["x-sw-request-type"] = [
{ key: "x-sw-request-type", value: type },
];
}
