CINXE.COM
Build "For you" recommendations using AI on Fastly! - DEV Community
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Build "For you" recommendations using AI on Fastly! - DEV Community</title> <meta name="last-updated" content="2024-12-22 23:51:00 UTC"> <meta name="user-signed-in" content="false"> <meta name="head-cached-at" content="1734911460"> <meta name="environment" content="production"> <link rel="stylesheet" href="https://assets.dev.to/assets/minimal-c6135f2dad61919fb9ad5b7407a14cce2369a2effce76540cfc808396cfc3a85.css" media="all" id="main-minimal-stylesheet" /> <link rel="stylesheet" href="https://assets.dev.to/assets/views-4dd5770daa5d9d443ed73a724fb1913af9b093295bf1a72307f0fb322e5df1d9.css" media="all" id="main-views-stylesheet" /> <link rel="stylesheet" href="https://assets.dev.to/assets/crayons-f94476cf86ce9153627fd53a77e651ec54b82b6be20ddb1bf93e3dd40b81aeab.css" media="all" id="main-crayons-stylesheet" /> <script src="https://assets.dev.to/assets/base-e0fa4f0ebd0f1ec157e6b07c08f9222f8c0cca3d10cfde4cd8c951b43985b10b.js" defer="defer"></script> <script src="https://assets.dev.to/assets/application-147cebefc5c4cddde055e8f5eb0055e811469b08405170e2411fbd7944b5ac04.js" defer="defer"></script> <script src="https://assets.dev.to/assets/baseInitializers-bc498cfd7bb7d2a2da59d68d0b2055cc2dd26fee3669ab88edbb396d37bc3369.js" defer="defer"></script> <script src="https://assets.dev.to/assets/baseTracking-b6bf73e5ee66633e151e7d5b7c6bbccedfa4c59e3615be97b98c4c0f543ddae7.js" defer="defer"></script> <meta name="search-script" content="https://assets.dev.to/assets/Search-cc5e8a352578866203771def747f37c3ec6a0869de0458328e0fcba3d5d2fceb.js"> <link rel="canonical" href="https://dev.to/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap" /> <meta name="description" content="Forget the hype; where is AI delivering real value? Let's use edge computing to harness the power of... Tagged with ai, fastly, rust, javascript."> <meta name="keywords" content="ai, fastly, rust, javascript, software, coding, development, engineering, inclusive, community"> <meta property="og:type" content="article" /> <meta property="og:url" content="https://dev.to/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap" /> <meta property="og:title" content="Build "For you" recommendations using AI on Fastly!" /> <meta property="og:description" content="Forget the hype; where is AI delivering real value? Let's use edge computing to harness the power of..." /> <meta property="og:site_name" content="DEV Community" /> <meta name="twitter:site" content="@thepracticaldev"> <meta name="twitter:creator" content="@"> <meta name="twitter:title" content="Build "For you" recommendations using AI on Fastly!"> <meta name="twitter:description" content="Forget the hype; where is AI delivering real value? Let's use edge computing to harness the power of..."> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:widgets:new-embed-design" content="on"> <meta name="robots" content="max-snippet:-1, max-image-preview:large, max-video-preview:-1"> <meta property="og:image" content="https://media2.dev.to/dynamic/image/width=1000,height=500,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vxm59xwk4zvhyquri0u.png" /> <meta name="twitter:image:src" content="https://media2.dev.to/dynamic/image/width=1000,height=500,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vxm59xwk4zvhyquri0u.png"> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> <link rel="icon" type="image/x-icon" href="https://media2.dev.to/dynamic/image/width=32,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png" /> <link rel="apple-touch-icon" href="https://media2.dev.to/dynamic/image/width=180,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png"> <link rel="apple-touch-icon" sizes="152x152" href="https://media2.dev.to/dynamic/image/width=152,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png"> <link rel="apple-touch-icon" sizes="180x180" href="https://media2.dev.to/dynamic/image/width=180,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png"> <link rel="apple-touch-icon" sizes="167x167" href="https://media2.dev.to/dynamic/image/width=167,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png"> <link href="https://media2.dev.to/dynamic/image/width=192,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png" rel="icon" sizes="192x192" /> <link href="https://media2.dev.to/dynamic/image/width=128,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png" rel="icon" sizes="128x128" /> <meta name="apple-mobile-web-app-title" content="dev.to"> <meta name="application-name" content="dev.to"> <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)"> <link rel="search" href="https://dev.to/open-search.xml" type="application/opensearchdescription+xml" title="DEV Community" /> <meta property="forem:name" content="DEV Community" /> <meta property="forem:logo" content="https://media2.dev.to/dynamic/image/width=512,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png" /> <meta property="forem:domain" content="dev.to" /> </head> <body class="sans-serif-article-body default-header" data-user-status="logged-out" data-community-name="DEV Community" data-subscription-icon="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" data-locale="en" data-honeybadger-key="hbp_nqu4Y66HuEKlD6YRGssZuRQnPOjDm50J8Zkr" data-deployed-at="2024-12-21T19:49:47Z" data-latest-commit-id="e6c13926910e7d2f69392fa2f2dd7544742cb452" data-ga-tracking="UA-71991109-1" data-cookie-banner-user-context="logged_out_only" data-cookie-banner-platform-context="off" data-algolia-id="PRSOBFP46H" data-algolia-search-key="9aa7d31610cba78851c9b1f63776a9dd" data-algolia-display="true" data-ga4-tracking-id="G-TYEM8Y3JN3"> <link rel="stylesheet" href="https://assets.dev.to/assets/minimal-c6135f2dad61919fb9ad5b7407a14cce2369a2effce76540cfc808396cfc3a85.css" media="all" id="secondary-minimal-stylesheet" /> <link rel="stylesheet" href="https://assets.dev.to/assets/views-4dd5770daa5d9d443ed73a724fb1913af9b093295bf1a72307f0fb322e5df1d9.css" media="all" id="secondary-views-stylesheet" /> <link rel="stylesheet" href="https://assets.dev.to/assets/crayons-f94476cf86ce9153627fd53a77e651ec54b82b6be20ddb1bf93e3dd40b81aeab.css" media="all" id="secondary-crayons-stylesheet" /> <div id="body-styles"> <style> :root { --accent-brand-lighter-rgb: 80, 99, 301; --accent-brand-rgb: 59, 73, 223; --accent-brand-darker-rgb: 47, 58, 178; } </style> </div> <div id="audiocontent" data-podcast=""> </div> <div class="navigation-progress" id="navigation-progress"></div> <header id="topbar" class="crayons-header topbar print-hidden"> <span id="route-change-target" tabindex="-1"></span> <a href="#main-content" class="skip-content-link">Skip to content</a> <div class="crayons-header__container"> <span class="inline-block m:hidden "> <button class="c-btn c-btn--icon-alone js-hamburger-trigger mx-2"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" role="img" aria-labelledby="aq2zh2gugu9kcratglqyvfsyk02fxoh1" class="crayons-icon"><title id="aq2zh2gugu9kcratglqyvfsyk02fxoh1">Navigation menu</title> <path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"></path> </svg> </button> </span> <a href="/" class="site-logo" aria-label="DEV Community Home"> <img class="site-logo__img" src="https://media2.dev.to/dynamic/image/quality=100/https://dev-to-uploads.s3.amazonaws.com/uploads/logos/resized_logo_UQww2soKuUsjaOGNB38o.png" style="aspect-ratio: 10 / 8" alt="DEV Community"> </a> <div class="crayons-header--search js-search-form" id="header-search"> <form accept-charset="UTF-8" method="get" action="/search" role="search"> <div class="crayons-fields crayons-fields--horizontal"> <div class="crayons-field flex-1 relative"> <input id="search-input" class="crayons-header--search-input crayons-textfield js-search-input" type="text" id="nav-search" name="q" placeholder="Find related posts..." autocomplete="off" /> <button type="submit" aria-label="Search" class="c-btn c-btn--icon-alone absolute inset-px right-auto mt-0 py-0"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" role="img" aria-labelledby="agbifvw0c2p5n4tdh20u7bmoacxvjm2g" aria-hidden="true" class="crayons-icon"><title id="agbifvw0c2p5n4tdh20u7bmoacxvjm2g">Search</title> <path d="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0111 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 01-1.969 5.617zm-2.006-.742A6.977 6.977 0 0018 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 004.875-1.975l.15-.15z"></path> </svg> </button> <a class="crayons-header--search-brand-indicator" href="https://www.algolia.com/developers/?utm_source=devto&utm_medium=referral" target="_blank" rel="noopener noreferrer"> Powered by <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" width="24" height="24" viewBox="0 0 500 500.34" role="img" aria-labelledby="a7rw291vwkip5a2s7p053fcj0x25wuir" aria-hidden="true" class="crayons-icon"><title id="a7rw291vwkip5a2s7p053fcj0x25wuir">Search</title> <defs></defs><path class="cls-1" d="M250,0C113.38,0,2,110.16,.03,246.32c-2,138.29,110.19,252.87,248.49,253.67,42.71,.25,83.85-10.2,120.38-30.05,3.56-1.93,4.11-6.83,1.08-9.52l-23.39-20.74c-4.75-4.22-11.52-5.41-17.37-2.92-25.5,10.85-53.21,16.39-81.76,16.04-111.75-1.37-202.04-94.35-200.26-206.1,1.76-110.33,92.06-199.55,202.8-199.55h202.83V407.68l-115.08-102.25c-3.72-3.31-9.43-2.66-12.43,1.31-18.47,24.46-48.56,39.67-81.98,37.36-46.36-3.2-83.92-40.52-87.4-86.86-4.15-55.28,39.65-101.58,94.07-101.58,49.21,0,89.74,37.88,93.97,86.01,.38,4.28,2.31,8.28,5.53,11.13l29.97,26.57c3.4,3.01,8.8,1.17,9.63-3.3,2.16-11.55,2.92-23.6,2.07-35.95-4.83-70.39-61.84-127.01-132.26-131.35-80.73-4.98-148.23,58.18-150.37,137.35-2.09,77.15,61.12,143.66,138.28,145.36,32.21,.71,62.07-9.42,86.2-26.97l150.36,133.29c6.45,5.71,16.62,1.14,16.62-7.48V9.49C500,4.25,495.75,0,490.51,0H250Z"></path> </svg> Algolia </a> </div> </div> </form> </div> <div class="flex items-center h-100 ml-auto"> <a class="c-link c-link--icon-alone c-link--block m:hidden mx-1" id="search-link" aria-label="Search" href="/search"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" role="img" aria-labelledby="agvb9dfarq8a287w8kmaeqp5aeeq85z7" class="crayons-icon"><title id="agvb9dfarq8a287w8kmaeqp5aeeq85z7">Search</title> <path d="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0111 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 01-1.969 5.617zm-2.006-.742A6.977 6.977 0 0018 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 004.875-1.975l.15-.15z"></path> </svg> </a> <div class="flex" id="authentication-top-nav-actions"> <span class="hidden m:block"> <a href="/enter" class="c-link c-link--block mr-2 whitespace-nowrap ml-auto" data-no-instant> Log in </a> </span> <a href="/enter?state=new-user" data-tracking-id="ca_top_nav" data-tracking-source="top_navbar" class="c-cta c-cta--branded whitespace-nowrap mr-2" data-no-instant> Create account </a> </div> </div> </div> </header> <div class="hamburger"> <div class="hamburger__content"> <header class="hamburger__content__header"> <h2 class="fs-l fw-bold flex-1 break-word lh-tight">DEV Community</h2> <button class="c-btn c-btn--icon-alone js-hamburger-trigger shrink-0" aria-label="Close"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" role="img" aria-labelledby="apr9rglurwywglhtir8ditmdxmh09ilq" aria-hidden="true" class="crayons-icon c-btn__icon"><title id="apr9rglurwywglhtir8ditmdxmh09ilq">Close</title><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636l4.95 4.95z"></path></svg> </button> </header> <div class="p-2 js-navigation-links-container" id="authentication-hamburger-actions"> </div> </div> <div class="hamburger__overlay js-hamburger-trigger"></div> </div> <div id="active-broadcast" class="broadcast-wrapper"></div> <div id="page-content" class="wrapper stories stories-show articletag-ai articletag-fastly articletag-rust articletag-javascript articleuser-909783 articleorg-2298" data-current-page="stories-show"> <div id="page-content-inner" data-internal-nav="false"> <div id="page-route-change" class="screen-reader-only" aria-live="polite" aria-atomic="true"></div> <style> .html-variant-wrapper { display: none} </style> <script src="https://unpkg.com/@webcomponents/webcomponentsjs@2.2.10/webcomponents-loader.js" integrity="sha384-3HK5hxQbkFqOIxMbpROlRmRtYl2LBZ52t+tqcjzsmr9NJuOWQxl8RgQSyFvq2lhy" crossorigin="anonymous" defer></script> <script src="https://assets.dev.to/assets/webShare-0686f0b9ac40589694ef6ae6a6202c44119bc781c254f6cf6d52d8a008461156.js" defer="defer"></script> <script src="https://assets.dev.to/assets/articlePage-e91bed0487621b06608474cfabffa186165e1f43f05b24581203ccc21e7aa45d.js" defer="defer"></script> <script src="https://assets.dev.to/assets/commentDropdowns-7a28d130e5b78d38b30a9495a964003a66bd64fa455fc70b766d69cf06b9ba24.js" defer="defer"></script> <script type="application/ld+json"> {"@context":"http://schema.org","@type":"Article","mainEntityOfPage":{"@type":"WebPage","@id":"https://dev.to/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap"},"url":"https://dev.to/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap","image":["https://media2.dev.to/dynamic/image/width=1080,height=1080,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vxm59xwk4zvhyquri0u.png","https://media2.dev.to/dynamic/image/width=1280,height=720,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vxm59xwk4zvhyquri0u.png","https://media2.dev.to/dynamic/image/width=1600,height=900,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vxm59xwk4zvhyquri0u.png"],"publisher":{"@context":"http://schema.org","@type":"Organization","name":"DEV Community","logo":{"@context":"http://schema.org","@type":"ImageObject","url":"https://media2.dev.to/dynamic/image/width=192,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png","width":"192","height":"192"}},"headline":"Build \"For you\" recommendations using AI on Fastly!","author":{"@context":"http://schema.org","@type":"Person","url":"https://dev.to/triblondon","name":"Andrew Betts"},"datePublished":"2024-08-07T11:48:17Z","dateModified":"2024-08-07T17:50:36Z"} </script> <div class="crayons-layout crayons-layout--3-cols crayons-layout--article"> <aside class="crayons-layout__sidebar-left" aria-label="Article actions"> <div class="crayons-article-actions print-hidden"> <div class="crayons-article-actions__inner"> <div class="reaction-drawer__outer hoverdown" style=""> <button id="reaction-drawer-trigger" aria-label="reaction-drawer-trigger" aria-pressed="false" class="hoverdown-trigger crayons-reaction pseudo-reaction crayons-tooltip__activator relative"> <span class="crayons-reaction__icon crayons-reaction__icon--borderless crayons-reaction--like crayons-reaction__icon--inactive" style="width: 40px; height: 40px"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" role="img" aria-hidden="true" class="crayons-icon"> <g clip-path="url(#clip0_988_3276)"> <path d="M19 14V17H22V19H18.999L19 22H17L16.999 19H14V17H17V14H19ZM20.243 4.75698C22.505 7.02498 22.583 10.637 20.479 12.992L19.059 11.574C20.39 10.05 20.32 7.65998 18.827 6.16998C17.324 4.67098 14.907 4.60698 13.337 6.01698L12.002 7.21498L10.666 6.01798C9.09103 4.60598 6.67503 4.66798 5.17203 6.17198C3.68203 7.66198 3.60703 10.047 4.98003 11.623L13.412 20.069L12 21.485L3.52003 12.993C1.41603 10.637 1.49503 7.01898 3.75603 4.75698C6.02103 2.49298 9.64403 2.41698 12 4.52898C14.349 2.41998 17.979 2.48998 20.242 4.75698H20.243Z" fill="#525252"></path> </g> <defs> <clipPath id="clip0_988_3276"> <rect width="24" height="24" fill="white"></rect> </clipPath> </defs> </svg> </span> <span class="crayons-reaction__icon crayons-reaction__icon--borderless crayons-reaction__icon--active" style="width: 40px; height: 40px"> <img aria_hidden="true" height="24" width="24" src="https://assets.dev.to/assets/heart-plus-active-9ea3b22f2bc311281db911d416166c5f430636e76b15cd5df6b3b841d830eefa.svg" /> </span> <span class="crayons-reaction__count" id="reaction_total_count"> <span class="bg-base-40 opacity-25 p-2 inline-block radius-default"></span> </span> <span class="crayons-tooltip__content"> Add reaction </span> </button> <div class="reaction-drawer" aria-expanded="false"> <div class="reaction-drawer__container"> <button id="reaction-butt-like" name="Like" aria-label="Like" aria-pressed="false" class="crayons-reaction crayons-tooltip__activator relative pt-2 pr-2 pb-1 pl-2" data-category="like"> <span class="crayons-reaction__icon crayons-reaction__icon--inactive p-0"> <img aria_hidden="true" height="32" width="32" src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" /> </span> <span class="crayons-reaction__count" id="reaction-number-like"><span class="bg-base-40 opacity-25 p-2 inline-block radius-default"></span></span> <span data-testid="tooltip" class="crayons-tooltip__content"> Like </span> </button> <button id="reaction-butt-unicorn" name="Unicorn" aria-label="Unicorn" aria-pressed="false" class="crayons-reaction crayons-tooltip__activator relative pt-2 pr-2 pb-1 pl-2" data-category="unicorn"> <span class="crayons-reaction__icon crayons-reaction__icon--inactive p-0"> <img aria_hidden="true" height="32" width="32" src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" /> </span> <span class="crayons-reaction__count" id="reaction-number-unicorn"><span class="bg-base-40 opacity-25 p-2 inline-block radius-default"></span></span> <span data-testid="tooltip" class="crayons-tooltip__content"> Unicorn </span> </button> <button id="reaction-butt-exploding_head" name="Exploding Head" aria-label="Exploding Head" aria-pressed="false" class="crayons-reaction crayons-tooltip__activator relative pt-2 pr-2 pb-1 pl-2" data-category="exploding_head"> <span class="crayons-reaction__icon crayons-reaction__icon--inactive p-0"> <img aria_hidden="true" height="32" width="32" src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" /> </span> <span class="crayons-reaction__count" id="reaction-number-exploding_head"><span class="bg-base-40 opacity-25 p-2 inline-block radius-default"></span></span> <span data-testid="tooltip" class="crayons-tooltip__content"> Exploding Head </span> </button> <button id="reaction-butt-raised_hands" name="Raised Hands" aria-label="Raised Hands" aria-pressed="false" class="crayons-reaction crayons-tooltip__activator relative pt-2 pr-2 pb-1 pl-2" data-category="raised_hands"> <span class="crayons-reaction__icon crayons-reaction__icon--inactive p-0"> <img aria_hidden="true" height="32" width="32" src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" /> </span> <span class="crayons-reaction__count" id="reaction-number-raised_hands"><span class="bg-base-40 opacity-25 p-2 inline-block radius-default"></span></span> <span data-testid="tooltip" class="crayons-tooltip__content"> Raised Hands </span> </button> <button id="reaction-butt-fire" name="Fire" aria-label="Fire" aria-pressed="false" class="crayons-reaction crayons-tooltip__activator relative pt-2 pr-2 pb-1 pl-2" data-category="fire"> <span class="crayons-reaction__icon crayons-reaction__icon--inactive p-0"> <img aria_hidden="true" height="32" width="32" src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" /> </span> <span class="crayons-reaction__count" id="reaction-number-fire"><span class="bg-base-40 opacity-25 p-2 inline-block radius-default"></span></span> <span data-testid="tooltip" class="crayons-tooltip__content"> Fire </span> </button> </div> </div> </div> <button id="reaction-butt-comment" aria-label="Jump to Comments" aria-pressed="false" class="crayons-reaction crayons-reaction--comment crayons-tooltip__activator relative" data-category="comment"> <span class="crayons-reaction__icon crayons-reaction__icon--borderless crayons-reaction__icon--inactive"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" role="img" aria-hidden="true" class="crayons-icon"> <path d="M10 3h4a8 8 0 010 16v3.5c-5-2-12-5-12-11.5a8 8 0 018-8zm2 14h2a6 6 0 000-12h-4a6 6 0 00-6 6c0 3.61 2.462 5.966 8 8.48V17z"></path> </svg> </span> <span class="crayons-reaction__count" id="reaction-number-comment" data-count="0"> <span class="bg-base-40 opacity-25 p-2 inline-block radius-default"></span> </span> <span data-testid="tooltip" class="crayons-tooltip__content"> Jump to Comments </span> </button> <button id="reaction-butt-readinglist" aria-label="Add to reading list" aria-pressed="false" class="crayons-reaction crayons-reaction--readinglist crayons-tooltip__activator relative" data-category="readinglist"> <span class="crayons-reaction__icon crayons-reaction__icon--borderless crayons-reaction__icon--inactive"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" role="img" aria-hidden="true" class="crayons-icon"> <path d="M5 2h14a1 1 0 011 1v19.143a.5.5 0 01-.766.424L12 18.03l-7.234 4.536A.5.5 0 014 22.143V3a1 1 0 011-1zm13 2H6v15.432l6-3.761 6 3.761V4z"></path> </svg> </span> <span class="crayons-reaction__count" id="reaction-number-readinglist"><span class="bg-base-40 opacity-25 p-2 inline-block radius-default"></span></span> <span data-testid="tooltip" class="crayons-tooltip__content"> Save </span> </button> <button id="reaction-butt-boost" aria-label="Boost" aria-pressed="false" class="crayons-reaction crayons-reaction--boost crayons-tooltip__activator relative"> <span class="crayons-reaction__icon crayons-reaction__icon--borderless crayons-reaction__icon--inactive"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" role="img" aria-hidden="true" class="crayons-icon" width="24" height="24"> <path transform="translate(24,0) scale(-1,1)" d="M6 4H21C21.5523 4 22 4.44772 22 5V12H20V6H6V9L1 5L6 1V4ZM18 20H3C2.44772 20 2 19.5523 2 19V12H4V18H18V15L23 19L18 23V20Z"></path> </svg> </span> <span data-testid="tooltip" class="crayons-tooltip__content"> Boost </span> </button> <div id="mod-actions-menu-btn-area" class="print-hidden trusted-visible-block align-center"> </div> <div class="align-center m:relative"> <button id="article-show-more-button" aria-controls="article-show-more-dropdown" aria-expanded="false" aria-haspopup="true" class="dropbtn crayons-btn crayons-btn--ghost-dimmed crayons-btn--icon-rounded" aria-label="Share post options"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" role="img" aria-labelledby="areeiyr316kc0z77ygyvltleqf13bb6y" aria-hidden="true" class="crayons-icon dropdown-icon"><title id="areeiyr316kc0z77ygyvltleqf13bb6y">More...</title><path fill-rule="evenodd" clip-rule="evenodd" d="M7 12a2 2 0 11-4 0 2 2 0 014 0zm7 0a2 2 0 11-4 0 2 2 0 014 0zm5 2a2 2 0 100-4 2 2 0 000 4z"></path></svg> </button> <div id="article-show-more-dropdown" class="crayons-dropdown side-bar left-2 right-2 m:right-auto m:left-100 s:left-auto mb-1 m:mb-0 top-unset bottom-100 m:top-0 m:bottom-unset"> <div> <button id="copy-post-url-button" class="flex justify-between crayons-link crayons-link--block w-100 bg-transparent border-0" data-postUrl="https://dev.to/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap"> <span class="fw-bold">Copy link</span> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" id="article-copy-icon" role="img" aria-labelledby="a3d236r5tu5mvpguz7cg3eqd8qwn270f" aria-hidden="true" class="crayons-icon mx-2 shrink-0"><title id="a3d236r5tu5mvpguz7cg3eqd8qwn270f">Copy link</title> <path d="M7 6V3a1 1 0 011-1h12a1 1 0 011 1v14a1 1 0 01-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1 1 0 013 21l.003-14c0-.552.45-1 1.007-1H7zm2 0h8v10h2V4H9v2zm-2 5v2h6v-2H7zm0 4v2h6v-2H7z"></path> </svg> </button> <div id="article-copy-link-announcer" aria-live="polite" class="crayons-notice crayons-notice--success my-2 p-1" aria-live="polite" hidden>Copied to Clipboard</div> </div> <div class="Desktop-only"> <a target="_blank" class="crayons-link crayons-link--block" rel="noopener" href='https://twitter.com/intent/tweet?text=%22Build%20%22For%20you%22%20recommendations%20using%20AI%20on%20Fastly%21%22%20by%20Andrew%20Betts%20%23DEVCommunity%20https%3A%2F%2Fdev.to%2Ffastly%2Fbuild-for-you-recommendations-using-ai-on-fastly-5eap'> Share to X </a> <a target="_blank" class="crayons-link crayons-link--block" rel="noopener" href="https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fdev.to%2Ffastly%2Fbuild-for-you-recommendations-using-ai-on-fastly-5eap&title=Build%20%22For%20you%22%20recommendations%20using%20AI%20on%20Fastly%21&summary=Forget%20the%20hype%3B%20where%20is%20AI%20delivering%20real%20value%3F%20Let%27s%20use%20edge%20computing%20to%20harness%20the%20power%20of...&source=DEV%20Community"> Share to LinkedIn </a> <a target="_blank" class="crayons-link crayons-link--block" rel="noopener" href="https://www.facebook.com/sharer.php?u=https%3A%2F%2Fdev.to%2Ffastly%2Fbuild-for-you-recommendations-using-ai-on-fastly-5eap"> Share to Facebook </a> <a target="_blank" class="crayons-link crayons-link--block" rel="noopener" href="https://toot.kytta.dev/?text=https%3A%2F%2Fdev.to%2Ffastly%2Fbuild-for-you-recommendations-using-ai-on-fastly-5eap"> Share to Mastodon </a> </div> <web-share-wrapper shareurl="https://dev.to/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap" sharetitle="Build "For you" recommendations using AI on Fastly!" sharetext="Forget the hype; where is AI delivering real value? Let's use edge computing to harness the power of..." template="web-share-button"> </web-share-wrapper> <template id="web-share-button"> <a href="#" class="dropdown-link-row crayons-link crayons-link--block">Share Post via...</a> </template> <a href="/report-abuse" class="crayons-link crayons-link--block">Report Abuse</a> </div> </div> </div> </div> </aside> <main id="main-content" class="crayons-layout__content grid gap-4"> <div class="article-wrapper"> <article class="crayons-card crayons-article mb-4" id="article-show-container" data-article-id="1950975" data-article-slug="build-for-you-recommendations-using-ai-on-fastly-5eap" data-author-id="909783" data-author-name="Andrew Betts" data-author-username="fastly" data-co-author-ids="" data-path="/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap" data-pin-path="/stories/feed/pinned_article" data-pinned-article-id="" data-published="true" data-scheduled="false" lang=en > <header class="crayons-article__header" id="main-title"> <div class="crayons-article__header__meta"> <div class="flex s:items-start flex-col s:flex-row"> <div id="action-space" class="crayons-article__actions mb-4 s:mb-0 s:order-last"></div> <div class="flex flex-1 mb-5 items-start"> <div class="relative"> <a href="/fastly"><img src="https://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2298%2F27608760-4be1-4e12-b054-b8a2748d978d.png" class="radius-default align-middle" width="40" height="40" alt="Fastly profile image"></a> <a href="/triblondon" class="absolute -right-2 -bottom-2 radius-full border border-solid border-2 border-base-inverted inline-flex"> <img class="radius-full align-middle" src="https://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F909783%2F77e76f40-7f25-481c-9d49-11add5fec33c.jpeg" width="24" height="24" alt="Andrew Betts" /> </a> </div> <div class="pl-3 flex-1"> <a href="/triblondon" class="crayons-link fw-bold">Andrew Betts</a> <span class="color-base-60"> for </span><a href="/fastly" class="crayons-link">Fastly</a> <p class="fs-xs color-base-60"> Posted on <time datetime="2024-08-07T11:48:17Z" class="date-no-year">Aug 7</time> </p> </div> </div> </div> <div class="multiple_reactions_engagement"> <span class="reaction_engagement_like hidden"> <img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24" /> <span id="reaction_engagement_like_count"> </span> </span> <span class="reaction_engagement_unicorn hidden"> <img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24" /> <span id="reaction_engagement_unicorn_count"> </span> </span> <span class="reaction_engagement_exploding_head hidden"> <img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24" /> <span id="reaction_engagement_exploding_head_count"> </span> </span> <span class="reaction_engagement_raised_hands hidden"> <img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24" /> <span id="reaction_engagement_raised_hands_count"> </span> </span> <span class="reaction_engagement_fire hidden"> <img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24" /> <span id="reaction_engagement_fire_count"> </span> </span> </div> <h1 class=" fs-3xl m:fs-4xl l:fs-5xl fw-bold s:fw-heavy lh-tight mb-2 medium"> Build "For you" recommendations using AI on Fastly! </h1> <div class="spec__tags flex flex-wrap"> <a class="crayons-tag " style=" --tag-bg: rgba(23, 253, 26, 0.10); --tag-prefix: #17fd1a; --tag-bg-hover: rgba(23, 253, 26, 0.10); --tag-prefix-hover: #17fd1a; " href="/t/ai"><span class="crayons-tag__prefix">#</span>ai</a> <a class="crayons-tag " style=" --tag-bg: rgba(59, 73, 223, 0.10); --tag-prefix: #3b49df; --tag-bg-hover: rgba(59, 73, 223, 0.10); --tag-prefix-hover: #3b49df; " href="/t/fastly"><span class="crayons-tag__prefix">#</span>fastly</a> <a class="crayons-tag " style=" --tag-bg: rgba(212, 161, 116, 0.10); --tag-prefix: #d4a174; --tag-bg-hover: rgba(212, 161, 116, 0.10); --tag-prefix-hover: #d4a174; " href="/t/rust"><span class="crayons-tag__prefix">#</span>rust</a> <a class="crayons-tag " style=" --tag-bg: rgba(247, 223, 30, 0.10); --tag-prefix: #f7df1e; --tag-bg-hover: rgba(247, 223, 30, 0.10); --tag-prefix-hover: #f7df1e; " href="/t/javascript"><span class="crayons-tag__prefix">#</span>javascript</a> </div> </div> </header> <div class="crayons-article__main "> <div class="crayons-article__body text-styles spec__body" data-article-id="1950975" id="article-body"> <p><strong>Forget the hype; where is AI delivering real value? Let's use edge computing to harness the power of AI and make smarter user experiences that are also fast, safe and reliable.</strong></p> <p>Recommendations are everywhere, and everyone knows that making web experiences more personalized makes them more engaging and successful. My Amazon homepage knows that I like home furnishings, kitchenware and right now, summer clothing:</p> <p><a href="https://media2.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0i01lz9a5ujgiv0fz94b.png" class="article-body-image-wrapper"><img src="https://media2.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0i01lz9a5ujgiv0fz94b.png" alt="Amazon homepage" loading="lazy" width="800" height="316"></a></p> <p>Today, most platforms make you choose between either being fast or being personalized. At Fastly, we think you — and your users — deserve to have both. If every time your web server generates a page, it is only suitable for one end user, you can't benefit from caching it, which is what edge networks like Fastly do well.</p> <p>So how can you benefit from edge caching, and yet make content personalized? We've written a lot before about how to break up complex client requests into multiple smaller, cacheable backend requests, and you'll find tutorials, code examples and demos in the <a href="https://www.fastly.com/documentation/solutions/use-cases/personalization/" target="_blank" rel="noopener noreferrer">personalization topic on our developer hub</a>.</p> <p>But what if you want to go further and generate the personalisation data <strong>at the edge</strong>? The "edge" - the Fastly servers handling your website's traffic, are the closest point to the end user that's still within your control. A great place to produce content that's specific to one user.</p> <h2> <a name="the-for-you-use-case" href="#the-for-you-use-case"> </a> The "For you" use case </h2> <p>Product recommendations are inherently transient, specific to an individual user and likely to change frequently. But they also don't need to persist - we don't typically need to know what we've recommended to each person, only whether a particular algorithm achieves better conversion than another. Some recommendation algorithms need access to a large amount of state data, like what users are most similar to you and their purchase or rating history, but often that data is easy to pregenerate in bulk.</p> <p>Basically, generating recommendations usually doesn't create a transaction, doesn't need any locks in your data store, and makes use of input data that's either immediately available from the current user's session, or created in an offline build process.</p> <p>Sounds like we can generate recommendations at the edge!</p> <h2> <a name="a-real-world-example" href="#a-real-world-example"> </a> A real world example </h2> <p>Let's take a look at the website of the <a href="https://www.metmuseum.org/" target="_blank" rel="noopener noreferrer">New York Metropolitan Museum of Art</a>:</p> <p><a href="https://media2.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqonzp3arru7fkkrcw7b5.png" class="article-body-image-wrapper"><img src="https://media2.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqonzp3arru7fkkrcw7b5.png" alt="Screenshot of metmuseum.org" loading="lazy" width="800" height="417"></a></p> <p>Each of the 500,000 or so objects in the Met's collection has a page with a picture and information about it. It also has this list of related objects:</p> <p><a href="https://media2.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ma5e06dtfmpeodju5pb.png" class="article-body-image-wrapper"><img src="https://media2.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ma5e06dtfmpeodju5pb.png" alt="Screenshot showing related items" loading="lazy" width="800" height="338"></a></p> <p>This seems to use a fairly straightforward system of <a href="https://www.nngroup.com/articles/filters-vs-facets/" target="_blank" rel="noopener noreferrer">faceting</a> to generate these relationships, showing me other artworks by the same artist, or other objects in the same wing of the museum, or which are also made of paper or originate in the same time period.</p> <p>The nice thing about this system (from a developer perspective!) is that since it's only based on the one input object, it can be pre-generated into the page.</p> <p>What if we want to augment this with a selection of recommendations that are based on the end user's personal browsing history as they navigate around the Met's website, not just based on this one object?</p> <h2> <a name="adding-personalized-recommendations" href="#adding-personalized-recommendations"> </a> Adding personalized recommendations </h2> <p>There's lots of ways we can do this, but I wanted to try using a language model, since AI is <em>happening</em> right now, and it's really different from the way the Met's existing related artworks mechanism seems to work. Here's the plan:</p> <ol> <li>Download <a href="https://www.metmuseum.org/about-the-met/policies-and-documents/open-access#get-started-header" target="_blank" rel="noopener noreferrer">the Met's open access collection dataset</a>.</li> <li>Run it through a language model to create <a href="https://www.pinecone.io/learn/vector-embeddings/" target="_blank" rel="noopener noreferrer">vector embeddings</a> – lists of numbers suitable for machine learning tasks.</li> <li>Build a performant similarity search engine for the resulting half a million vectors (representing the Met’s artworks) and load it into <a href="https://www.fastly.com/products/kv-store" target="_blank" rel="noopener noreferrer">KV store</a> so we can use it from <a href="https://www.fastly.com/products/edge-compute" target="_blank" rel="noopener noreferrer">Fastly Compute</a>.</li> </ol> <p>Once we've done all that, we should be able to, as you browse the Met's website:</p> <ol> <li>Track the artworks you visit in a cookie.</li> <li>Look up the vectors corresponding to those artworks.</li> <li>Calculate an average vector representing your browsing interests.</li> <li>Plug that into our similarity search engine to find the most similar artworks.</li> <li>Load details about those artworks from the <a href="https://metmuseum.github.io/#object" target="_blank" rel="noopener noreferrer">Met's Object API</a> and augment the page with personalized recommendations.</li> </ol> <p>Et voilà, personalized recommendations:</p> <p><a href="https://media2.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbtfh08pl81kz2ntie1sq.png" class="article-body-image-wrapper"><img src="https://media2.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbtfh08pl81kz2ntie1sq.png" alt="Screenshot of personalized recommendations" loading="lazy" width="800" height="325"></a></p> <p>OK, so let's break that down.</p> <h2> <a name="creating-the-dataset" href="#creating-the-dataset"> </a> Creating the dataset </h2> <p>The Met's raw dataset is a CSV with lots of columns and looks like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>Object Number,Is Highlight,Is Timeline Work,Is Public Domain,Object ID,Gallery Number,Department,AccessionYear,Object Name,Title,Culture,Period,Dynasty,Reign,Portfolio,Constituent ID,Artist Role,Artist Prefix,Artist Display Name,Artist Display Bio,Artist Suffix,Artist Alpha Sort,Artist Nationality,Artist Begin Date,Artist End Date,Artist Gender,Artist ULAN URL,Artist Wikidata URL,Object Date,Object Begin Date,Object End Date,Medium,Dimensions,Credit Line,Geography Type,City,State,County,Country,Region,Subregion,Locale,Locus,Excavation,River,Classification,Rights and Reproduction,Link Resource,Object Wikidata URL,Metadata Date,Repository,Tags,Tags AAT URL,Tags Wikidata URL 1979.486.1,False,False,False,1,,The American Wing,1979,Coin,One-dollar Liberty Head Coin,,,,,,16429,Maker," ",James Barton Longacre,"American, Delaware County, Pennsylvania 1794–1869 Philadelphia, Pennsylvania"," ","Longacre, James Barton",American,1794 ,1869 ,,http://vocab.getty.edu/page/ulan/500011409,https://www.wikidata.org/wiki/Q3806459,1853,1853,1853,Gold,Dimensions unavailable,"Gift of Heinz L. Stoppelmann, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/1,,,"Metropolitan Museum of Art, New York, NY",,, 1980.264.5,False,False,False,2,,The American Wing,1980,Coin,Ten-dollar Liberty Head Coin,,,,,,107,Maker," ",Christian Gobrecht,1785–1844," ","Gobrecht, Christian",American,1785 ,1844 ,,http://vocab.getty.edu/page/ulan/500077295,https://www.wikidata.org/wiki/Q5109648,1901,1901,1901,Gold,Dimensions unavailable,"Gift of Heinz L. Stoppelmann, 1980",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/2,,,"Metropolitan Museum of Art, New York, NY",,, </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>Simple enough to <a href="https://github.com/fastly/edgeml-recommender/blob/main/scripts/preprocess.py" target="_blank" rel="noopener noreferrer">transform that into two columns</a>, an ID and a string:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>id,description 1,"One-dollar Liberty Head Coin; Type: Coin; Artist: James Barton Longacre; Medium: Gold; Date: 1853; Credit: Gift of Heinz L. Stoppelmann, 1979" 2,"Ten-dollar Liberty Head Coin; Type: Coin; Artist: Christian Gobrecht; Medium: Gold; Date: 1901; Credit: Gift of Heinz L. Stoppelmann, 1980" 3,"Two-and-a-Half Dollar Coin; Type: Coin; Medium: Gold; Date: 1927; Credit: Gift of C. Ruxton Love Jr., 1967" </code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>Now we can use the <a href="https://pypi.org/project/transformers/" target="_blank" rel="noopener noreferrer">transformers</a> package from <a href="https://huggingface.co/" target="_blank" rel="noopener noreferrer">Hugging Face AI toolset</a>, and generate embeddings of each of these descriptions. We <a href="https://github.com/fastly/edgeml-recommender/blob/main/scripts/create-embeddings.py" target="_blank" rel="noopener noreferrer">used</a> the <code>sentence-transformers/all-MiniLM-L12-v2</code> model, and used <a href="https://www.ibm.com/topics/principal-component-analysis" target="_blank" rel="noopener noreferrer">principal component analysis</a> (PCA) to reduce the resulting vectors to 5 dimensions. That gives you something like:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="mf">-0.005544120445847511</span><span class="p">,</span><span class="w"> </span><span class="mf">-0.030924081802368164</span><span class="p">,</span><span class="w"> </span><span class="mf">0.008597176522016525</span><span class="p">,</span><span class="w"> </span><span class="mf">0.20186401903629303</span><span class="p">,</span><span class="w"> </span><span class="mf">0.0578165128827095</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="mf">-0.005544120445847511</span><span class="p">,</span><span class="w"> </span><span class="mf">-0.030924081802368164</span><span class="p">,</span><span class="w"> </span><span class="mf">0.008597176522016525</span><span class="p">,</span><span class="w"> </span><span class="mf">0.20186401903629303</span><span class="p">,</span><span class="w"> </span><span class="mf">0.0578165128827095</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="err">…</span><span class="w"> </span><span class="p">]</span><span class="w"> </span></code></pre> <div class="highlight__panel js-actions-panel"> <div class="highlight__panel-action js-fullscreen-code-action"> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title> <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title> <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path> </svg> </div> </div> </div> <p>We have half a million of these, so it's not possible to store this entire dataset within the edge app's memory. And we want to do a custom type of similarity search over this data, which is something a traditional key-value store doesn't offer. Since we’re building a real-time experience, we also really want to avoid having to search half a million vectors at a time.</p> <p>So, let's <a href="https://github.com/fastly/edgeml-recommender/blob/main/scripts/partition.py" target="_blank" rel="noopener noreferrer">partition the data</a>. We can use <a href="https://en.wikipedia.org/wiki/K-means_clustering" target="_blank" rel="noopener noreferrer">KMeans</a> clustering to group vectors that are similar to each other. We sliced the data into 500 clusters of varying sizes, and calculated a center point called a “centroid vector” for each of those clusters. If you plotted this vector space in two dimensions and zoomed in, it might look a bit like this:</p> <p><a href="https://media2.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9mxsvulzntdwlxtos5od.png" class="article-body-image-wrapper"><img src="https://media2.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9mxsvulzntdwlxtos5od.png" alt="Clustering illustration" loading="lazy" width="800" height="495"></a></p> <p>The red crosses are the mathematical center points of each cluster of vectors, called centroids. They can work like wayfinders for our half-million-vector space. For instance, if we want to find the 10 most similar vectors to a given vector A, we can first look for the nearest centroid (out of 500), then conduct our search only within its corresponding cluster–a much more manageable area! </p> <p>Now we have 500 small datasets and an index that maps centroid points to the relevant dataset. Next, to enable real-time performance, we want to precompile search graphs so that we don't need to initialize and construct them at runtime, and can use as little CPU time as possible. A really fast nearest-neighbor algorithm is <a href="https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world" target="_blank" rel="noopener noreferrer">Hierarchical Navigable Small Worlds</a> (HNSW), and it has a <a href="https://github.com/instant-labs/instant-distance" target="_blank" rel="noopener noreferrer">pure Rust implementation</a>, which we're using to write our edge app. So we wrote a <a href="https://github.com/fastly/edgeml-recommender/blob/main/precompiler/src/main.rs" target="_blank" rel="noopener noreferrer">small standalone Rust app</a> to construct the HNSW graph structs for each dataset, and then used <a href="https://crates.io/crates/bincode" target="_blank" rel="noopener noreferrer">bincode</a> to export the memory of the instantiated struct into a binary blob.</p> <p>Now, those binary blobs can be loaded into <a href="https://www.fastly.com/documentation/guides/concepts/edge-state/data-stores/" target="_blank" rel="noopener noreferrer">KV store</a>, keyed against the cluster index, and the cluster index can be included in our edge app. </p> <p>This architecture lets us load parts of the search index into memory on demand. And since we’ll never have to search more than a few thousand vectors at a time, our searches will always be cheap and fast.</p> <h2> <a name="building-the-edge-app" href="#building-the-edge-app"> </a> Building the edge app </h2> <p>The application that we run at the edge needs to handle several types of requests:</p> <ul> <li> <strong>HTML pages:</strong> We fetch these from <code>metmuseum.org</code> and transform the response to add extra front-end <code><script></code> and <code><style></code> tags, so we can inject a bit of our own front end processing and content</li> <li> <strong>The Fastly script and style resources</strong> referenced by those extra tags, which we can serve directly out of the edge app's binary.</li> <li> <strong>The recommender endpoint</strong>, which generates and returns the recommendations.</li> <li> <strong>All other (non-HTML) requests:</strong> Images, and the Met's own scripts and stylesheets, which we proxy directly from their domain without alteration.</li> </ul> <p>We initially <a href="https://github.com/fastly/edgeml-recommender/blob/main/met-example/src/index.js" target="_blank" rel="noopener noreferrer">built this app in JavaScript</a>, but ended up <a href="https://github.com/fastly/edgeml-recommender/blob/main/recommender/src/main.rs" target="_blank" rel="noopener noreferrer">porting the recommender part to Rust</a> because we liked the HNSW implementation in <a href="https://docs.rs/instant-distance" target="_blank" rel="noopener noreferrer">instant-distance</a>.</p> <p>The <a href="https://github.com/fastly/edgeml-recommender/blob/main/met-example/src/assets/client.js" target="_blank" rel="noopener noreferrer">client side JavaScript</a> does a few interesting things:</p> <ol> <li>Using <a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver" target="_blank" rel="noopener noreferrer">IntersectionObserver</a>, we trigger an event when the user scrolls down the page to the related objects section. This is a super efficient API that's much better than using older methods like <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scroll_event" target="_blank" rel="noopener noreferrer">onscroll</a>.</li> <li>Make a fetch to our special recommendations API endpoint (which we can then handle at the edge and return object information)</li> <li>Compose some HTML using a template built into a client-side function</li> <li>Append that HTML to the page and move the intersection observer to the new element so as you scroll through the recommendations, we keep loading more.</li> </ol> <p>This way, we can deliver the main HTML payload without invoking our recommendation algorithm, but the recommendations are delivered fast enough that we can load them as you scroll and they'll almost certainly be there by the time you get to them.</p> <p>I like doing things this way because getting that first above-the-fold view to the user as fast as possible is absolutely paramount. Anything that you can't see unless you scroll can be loaded later, and especially if it is a complex piece of personalized content - there's no point generating it if the user isn't planning to scroll.</p> <h2> <a name="closing-thoughts" href="#closing-thoughts"> </a> Closing thoughts </h2> <p>So now you have the best of both worlds: the ability to serve highly personalized content, almost never requiring any blocking fetches to origin, and an optimized HTML payload that renders incredibly fast, allowing your application to enjoy effectively limitless scalability and near perfect resilience.</p> <p>It's not a perfect solution. It'd be great if Fastly offered more higher level features to expose edge data via query mechanisms other than a simple key lookup (let us know if that would help you!) and this specific mechanism has obvious flaws - if I have separate interests in two or more very different things (say 19th century oil paintings and ancient Roman amphora) I would get recommendations which would be the theoretical semantic "middle point" between those, not a very useful result.</p> <p>Still, hopefully this demonstrates the principle that figuring out how to do work at the edge often results in outsized benefits in terms of scalability, performance and resilience.</p> <p>Let us know what you build <a href="https://community.fastly.com/t/insert-ai-generated-personalized-for-you-content-at-the-edge-why-not/2855" target="_blank" rel="noopener noreferrer">on our community thread</a>!</p> </div> <div class="js-billboard-container body-billboard-container" data-async-url="/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap/billboards/post_body_bottom"></div> </div> <section id="comments" data-follow-button-container="true" data-updated-at="2024-12-22 23:51:00 UTC" class="text-padding mb-4 border-t-1 border-0 border-solid border-base-10"> <header class="relative flex justify-between items-center mb-6"> <div class="flex items-center"> <h2 class="crayons-subtitle-1"> Top comments <span class="js-comments-count" data-comments-count="0">(0)</span> </h2> </div> <div id="comment-subscription" class="print-hidden"> <div class="crayons-btn-group"> <span class="crayons-btn crayons-btn--outlined">Subscribe</span> </div> </div> </header> <div id="billboard_delay_trigger"></div> <div id="comments-container" data-testid="comments-container" data-commentable-id="1950975" data-commentable-type="Article" data-has-recent-comment-activity="false"> <div id="response-templates-data" class="hidden"></div> <form class="comment-form print-hidden" id="new_comment" action="/comments" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓" autocomplete="off" /> <input type="hidden" name="authenticity_token" value="NOTHING" id="new_comment_authenticity_token"> <input value="1950975" autocomplete="off" type="hidden" name="comment[commentable_id]" id="comment_commentable_id" /> <input value="Article" autocomplete="off" type="hidden" name="comment[commentable_type]" id="comment_commentable_type" /> <span class="crayons-avatar m:crayons-avatar--l mr-2 shrink-0"> <img src="https://media2.dev.to/dynamic/image/width=256,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png" width="32" height="32" alt="pic" class="crayons-avatar__image overflow-hidden" id="comment-primary-user-profile--avatar" loading="lazy" /> </span> <div class="comment-form__inner"> <div class="comment-form__field" data-tracking-name="comment_form_textfield"> <textarea placeholder="Add to the discussion" onfocus="handleFocus(event)" onkeyup="handleKeyUp(event)" onkeydown="handleKeyDown(event)" oninput="handleChange(event)" id="text-area" required="required" class="crayons-textfield comment-textarea crayons-textfield--ghost" aria-label="Add a comment to the discussion" name="comment[body_markdown]"> </textarea> </div> <div class="response-templates-container crayons-card crayons-card--secondary p-4 mb-4 comment-form__templates fs-base hidden"> <header class="mb-3"> <button type="button" class="crayons-btn personal-template-button active" data-target-type="personal" data-form-id="new_comment">Personal</button> <button type="button" class="crayons-btn moderator-template-button hidden" data-target-type="moderator" data-form-id="new_comment">Trusted User</button> </header> <div class="personal-responses-container"> </div> <div class="moderator-responses-container hidden"> </div> <a target="_blank" rel="noopener nofollow" href="/settings/response-templates"> Create template </a> <p>Templates let you quickly answer FAQs or store snippets for re-use.</p> </div> <div class="comment-form__preview text-styles text-styles--secondary" id="preview-div"></div> <div class="comment-form__buttons mb-4"> <button type="submit" class="crayons-btn mr-2 js-btn-enable" onclick="validateField(event)" data-tracking-name="comment_submit_button" disabled>Submit</button> <button type="button" class="preview-toggle crayons-btn crayons-btn--secondary comment-action-preview js-btn-enable mr-2" data-tracking-name="comment_preview_button" disabled>Preview</button> <a href="/404.html" class="dismiss-edit-comment crayons-btn crayons-btn--ghost js-btn-dismiss hidden">Dismiss</a> </div> </div> <div class="code-of-conduct" id="toggle-code-of-conduct-checkbox"></div> </form> <div class="comments" id="comment-trees-container"> </div> </div> <div class="align-center"> <nav class="fs-s align-center block" aria-label="Conduct controls"> <a href="/code-of-conduct" class="crayons-link crayons-link--secondary">Code of Conduct</a> <span class="opacity-25 px-2" role="presentation">•</span> <a href="/report-abuse" class="crayons-link crayons-link--secondary">Report abuse</a> </nav> </div> </section> <div id="hide-comments-modal" class="hidden"> <form id="hide-comments-modal__form" class="hide-comments-modal__form" data-type="json" action="/comments/hide" accept-charset="UTF-8" data-remote="true" method="post"><input name="utf8" type="hidden" value="✓" autocomplete="off" /><input type="hidden" name="_method" value="patch" autocomplete="off" /><input type="hidden" name="authenticity_token" value="qFf2fWLiS4TbQo4N26SOiZM_LBS8Wz8ii8aRwoDrlHEk8qQL1oZTRngLjGkXV1wxHloUcXxl_KteBeBHY3ZIHw" autocomplete="off" /> <div class="hide-comments-modal__content"> <p class="pb-2"> Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's <a id="hide-comments-modal__comment-permalink" href="#">permalink</a>. </p> <label class="crayons-field crayons-field--checkbox my-2"> <input name="hide_children" type="hidden" value="0" autocomplete="off" /><input class="hide_children crayons-checkbox" type="checkbox" value="1" name="hide_children" id="hide_children" /> <p class="crayons-field__label">Hide child comments as well</p> </label> <p class="pb-4 pt-2"> <button type="submit" class="crayons-btn"> Confirm </button> </p> </div> </form> <p class="fs-s color-base-60">For further actions, you may consider blocking this person and/or <a id="hide-comments-modal__report-link" href="/report-abuse">reporting abuse</a></p> </div> </article> <div class="pb-4 crayons-layout__comments-billboard"> <div class="js-billboard-container pb-4 crayons-layout__comments-billboard" data-async-url="/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap/billboards/post_comments"></div> </div> <section class="crayons-card crayons-card--secondary text-padding mb-4 print-hidden" id="bottom-content-read-next"> <h2 class="crayons-subtitle-1">Read next</h2> <a href="/kainaat_sahni_d4aee463d0d/ai-meets-supply-chains-strategic-deployment-and-supplier-innovation-by-shubham-r-ekatpure-45m8" data-preload-image="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flc65eumnrjl5l7q77k7x.PNG" class="crayons-link mt-6 block"> <div class="flex items-center"> <span class="crayons-avatar crayons-avatar--xl m:crayons-avatar--2xl mr-4 shrink-0"> <img loading="lazy" alt="kainaat_sahni_d4aee463d0d profile image" class="crayons-avatar__image" width="100" height="100" src="https://media2.dev.to/dynamic/image/width=100,height=100,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2569640%2Fad3d54c3-87de-4c1b-ac25-ff9154c09a44.jpg" /> </span> <div> <h3 class="fs-xl mb-0 lh-tight">AI Meets Supply Chains: Strategic Deployment and Supplier Innovation by Shubham R. Ekatpure</h3> <p class="opacity-75 pt-1"> Kainaat Sahni - <time datetime="2024-12-15T18:23:26Z">Dec 15</time> </p> </div> </div> </a> <a href="/rawas_aditya/understanding-the-barrel-pattern-in-javascripttypescript-19n2" data-preload-image="" class="crayons-link mt-6 block"> <div class="flex items-center"> <span class="crayons-avatar crayons-avatar--xl m:crayons-avatar--2xl mr-4 shrink-0"> <img loading="lazy" alt="rawas_aditya profile image" class="crayons-avatar__image" width="100" height="100" src="https://media2.dev.to/dynamic/image/width=100,height=100,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F283242%2F1e0f823e-2190-4a89-acc5-79425295a9f3.jpg" /> </span> <div> <h3 class="fs-xl mb-0 lh-tight">Understanding the Barrel Pattern in JavaScript/TypeScript</h3> <p class="opacity-75 pt-1"> Aditya Rawas - <time datetime="2024-12-15T11:59:01Z">Dec 15</time> </p> </div> </div> </a> <a href="/airbyte/can-ai-finally-generate-best-practice-code-i-think-so-55d2" data-preload-image="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11wgie5yieyi0d9ylvzv.png" class="crayons-link mt-6 block"> <div class="flex items-center"> <span class="crayons-avatar crayons-avatar--xl m:crayons-avatar--2xl mr-4 shrink-0"> <img loading="lazy" alt="quintonwall profile image" class="crayons-avatar__image" width="100" height="100" src="https://media2.dev.to/dynamic/image/width=100,height=100,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1707939%2F7764300f-d591-4504-ab92-6169ee7f7f79.jpg" /> </span> <div> <h3 class="fs-xl mb-0 lh-tight">Can AI finally generate best practice code? I think so.</h3> <p class="opacity-75 pt-1"> Quinton - <time datetime="2024-12-19T21:05:14Z">Dec 19</time> </p> </div> </div> </a> <a href="/shishsingh/gemini-20-a-new-era-of-ai-2d7f" data-preload-image="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxkkxn14maqnb82ixygy5.png" class="crayons-link mt-6 block"> <div class="flex items-center"> <span class="crayons-avatar crayons-avatar--xl m:crayons-avatar--2xl mr-4 shrink-0"> <img loading="lazy" alt="shishsingh profile image" class="crayons-avatar__image" width="100" height="100" src="https://media2.dev.to/dynamic/image/width=100,height=100,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1038898%2F554c71dd-8bae-480c-ad50-e468b9bbe86b.jpg" /> </span> <div> <h3 class="fs-xl mb-0 lh-tight">Gemini 2.0: A New Era of AI</h3> <p class="opacity-75 pt-1"> Shish Singh - <time datetime="2024-12-19T19:02:08Z">Dec 19</time> </p> </div> </div> </a> </section> </div> </main> <aside class="crayons-layout__sidebar-right" aria-label="Author details"> <div class="crayons-article-sticky grid gap-4 pb-4 break-word" id="article-show-primary-sticky-nav"> <div class="crayons-card crayons-card--secondary branded-7 p-4 pt-0 gap-4 grid" style="border-top-color: #e02100;"> <div class="-mt-4"> <a href="/fastly" class="flex"> <span class="crayons-logo crayons-logo--xl mr-2 shrink-0"> <img src="https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2298%2F27608760-4be1-4e12-b054-b8a2748d978d.png" class="crayons-logo__image" alt="" loading="lazy" /> </span> <span class="crayons-link crayons-subtitle-2 mt-5"> Fastly </span> </a> </div> <div class="print-hidden"> <button name="button" type="button" data-info="{"className":"Organization","style":"","id":2298,"name":"Fastly"}" class="crayons-btn follow-action-button whitespace-nowrap w-100 " aria-label="Follow organization: Fastly" aria-pressed="false">Follow</button> </div> </div> <div class="crayons-card crayons-card--secondary"> <header class="crayons-card__header"> <h3 class="crayons-subtitle-2"> More from <a href="/fastly">Fastly</a> </h3> </header> <div> <a class="crayons-link crayons-link--contentful" href="/fastly/start-your-next-fastly-compute-javascript-application-with-npm-init-2h6"> Start your next Fastly Compute JavaScript application with npm init <div class="crayons-link__secondary -ml-1"> <span class="mr-1"><span class="opacity-50">#</span>fastly</span> <span class="mr-1"><span class="opacity-50">#</span>compute</span> <span class="mr-1"><span class="opacity-50">#</span>javascript</span> <span class="mr-1"><span class="opacity-50">#</span>npm</span> </div> </a> <a class="crayons-link crayons-link--contentful" href="/fastly/fastly-cli-on-npm-now-at-your-javascript-fingertips-19ce"> Fastly CLI on npm: now at your JavaScript fingertips <div class="crayons-link__secondary -ml-1"> <span class="mr-1"><span class="opacity-50">#</span>javascript</span> <span class="mr-1"><span class="opacity-50">#</span>npm</span> <span class="mr-1"><span class="opacity-50">#</span>fastly</span> </div> </a> <a class="crayons-link crayons-link--contentful" href="/fastly/a-modular-edge-side-includes-component-for-javascript-compute-563d"> A modular Edge Side Includes component for JavaScript Compute <div class="crayons-link__secondary -ml-1"> <span class="mr-1"><span class="opacity-50">#</span>fastly</span> <span class="mr-1"><span class="opacity-50">#</span>javascript</span> <span class="mr-1"><span class="opacity-50">#</span>edge</span> <span class="mr-1"><span class="opacity-50">#</span>esi</span> </div> </a> </div> </div> </div> <div class="crayons-article-sticky grid gap-4 break-word js-billboard-container" data-async-url="/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap/billboards/post_sidebar"></div> </aside> </div> <div class="mod-actions-menu print-hidden"></div> <div data-testid="unpublish-post-modal-container" class="unpublish-post-modal-container hidden"></div> <div class="fullscreen-code js-fullscreen-code"></div> <script src="https://assets.dev.to/assets/followButtons-f2455d1f50a862b83fa006b1953e3a1644598781243cae25d3e75b13c04184fd.js" defer="defer"></script> <script src="https://assets.dev.to/assets/billboard-a7ffd2b38d410444a4ef2d42c6ce905699d84b1520aed314b817a767ddc3b362.js" defer="defer"></script> <script src="https://assets.dev.to/assets/localizeArticleDates-70147c5c6bfe350b42e020ebb2a3dd37419d83978982b5a67b6389119bf162ac.js" defer="defer"></script> <script src="https://assets.dev.to/assets/articleReactions-79a44d82fb34a91c3987c24cfd3bcf2b0d38a02730b69ae6da297387d185f015.js" defer="defer"></script> <script> function activateRunkitTags() { if (!areAnyRunkitTagsPresent()) return var checkRunkit = setInterval(function() { try { dynamicallyLoadRunkitLibrary() if (typeof(RunKit) === 'undefined') { return } replaceTagContentsWithRunkitWidget() clearInterval(checkRunkit); } catch(e) { console.error(e); clearInterval(checkRunkit); } }, 200); } function isRunkitTagAlreadyActive(runkitTag) { return runkitTag.querySelector("iframe") !== null; }; function areAnyRunkitTagsPresent() { var presentRunkitTags = document.getElementsByClassName("runkit-element"); return presentRunkitTags.length > 0 } function replaceTagContentsWithRunkitWidget() { var targets = document.getElementsByClassName("runkit-element"); for (var i = 0; i < targets.length; i++) { if (isRunkitTagAlreadyActive(targets[i])) { continue; } var wrapperContent = targets[i].textContent; if (/^(<iframe src)/.test(wrapperContent) === false) { if (targets[i].children.length > 0) { var preamble = targets[i].children[0].textContent; var content = targets[i].children[1].textContent; targets[i].innerHTML = ""; var notebook = RunKit.createNotebook({ element: targets[i], source: content, preamble: preamble }); } } } }; function dynamicallyLoadRunkitLibrary() { if (typeof(dynamicallyLoadScript) === "undefined") return dynamicallyLoadScript("//embed.runkit.com") } activateRunkitTags(); </script> <div class="js-billboard-container pb-4 crayons-layout__comments-billboard" data-async-url="/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap/billboards/post_fixed_bottom"></div> <div id="runtime-banner-container"></div> </div> </div> <footer id="footer" class="crayons-footer print-hidden"> <div id="footer-container" class="crayons-footer__container"> <div style="" data-display-unit data-id="146443" data-category-click="click" data-category-impression="impression" data-context-type="home" data-special="nothing" data-article-id="332101" data-type-of="in_house"> <p style="font-weight: bold;margin-bottom: 10px"> Thank you to our Diamond Sponsor <a href="https://neon.tech/">Neon</a> for supporting our community. </p> </div> <p class="fs-s crayons-footer__description"> <a class="c-link c-link--branded fw-medium" aria-label="DEV Community Home" href="/">DEV Community</a> — A constructive and inclusive social network for software developers. With you every step of your journey. </p> <ul class="footer__nav-links flex gap-2 justify-center flex-wrap fs-s p-0" style="" /> <li class="footer__nav-link flex items-center"> <a href="/"> Home </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/++"> DEV++ </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/pod"> Podcasts </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/videos"> Videos </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/tags"> Tags </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/help"> DEV Help </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="https://shop.forem.com/"> Forem Shop </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/advertise"> Advertise on DEV </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/challenges"> DEV Challenges </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/showcase"> DEV Showcase </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/about"> About </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/contact"> Contact </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/free-postgres-database-tier"> Free Postgres Database </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/guides"> Guides </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/software-comparisons"> Software comparisons </a> <span class="dot ml-2"></span> </li> </ul> <ul class="footer__nav-links flex gap-2 justify-center flex-wrap fs-s p-0" style="" /> <li class="footer__nav-link flex items-center"> <a href="/code-of-conduct"> Code of Conduct </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/privacy"> Privacy Policy </a> <span class="dot ml-2"></span> </li> <li class="footer__nav-link flex items-center"> <a href="/terms"> Terms of use </a> <span class="dot ml-2"></span> </li> </ul> <div class="fs-s"> <p>Built on <a class="c-link c-link--branded" target="_blank" rel="noopener" href="https://www.forem.com">Forem</a> — the <a target="_blank" rel="noopener" class="c-link c-link--branded" href="https://dev.to/t/opensource">open source</a> software that powers <a target="_blank" rel="noopener" class="c-link c-link--branded" href="https://dev.to">DEV</a> and other inclusive communities.</p> <p>Made with love and <a target="_blank" rel="noopener" class="c-link c-link--branded" href="https://dev.to/t/rails">Ruby on Rails</a>. DEV Community <span title="copyright">©</span> 2016 - 2024.</p> </div> </div> </footer> <div id="snack-zone"></div> <div id="global-signup-modal" class="authentication-modal hidden"> <div class="authentication-modal__container"> <figure class="authentication-modal__image-container"> <img class="authentication-modal__image" src="https://media2.dev.to/dynamic/image/width=190,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png" alt="DEV Community" loading="lazy" /> </figure> <div class="authentication-modal__content"> <p class="authentication-modal__description"> We're a place where coders share, stay up-to-date and grow their careers. </p> </div> <div class="authentication-modal__actions"> <a href="/enter" class="crayons-btn" aria-label="Log in" data-no-instant> Log in </a> <a href="/enter?state=new-user" class="crayons-btn crayons-btn--ghost-brand js-global-signup-modal__create-account" aria-label="Create new account" data-no-instant> Create account </a> </div> </div> </div> <script src="https://assets.dev.to/assets/signupModalShortcuts-0b25469b985100a01e94cbd7fccaf7f0a4d776e129aac65c766aa32cb28ab29a.js" defer="defer"></script> <div id="cookie-consent"></div> <div id="i18n-translations" data-translations="{"en":{"core":{"add_comment":"Add comment","beta":"beta","comment":"Comment","copy_link":"Copy link","edit_profile":"Edit profile","follow":"Follow","follow_back":"Follow back","following":"Following","like":"Like","loading":"loading...","reaction":"Reaction","report_abuse":"Report abuse","search":"Search","success_settings":"Successfully updated settings.","counted_organization":{"one":"%{count} organization","other":"%{count} organizations"},"counted_user":{"one":"%{count} person","other":"%{count} people"},"not_following":"You're not following anyone","following_everyone":"You're following %{details} (everyone)","you_are_following":"You're following","and":"and"}}}"></div> <div id="reaction-category-resources" class="hidden" aria-hidden="true"> <img data-name="Like" data-slug="like" data-position="1" src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18" /> <img data-name="Unicorn" data-slug="unicorn" data-position="2" src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18" /> <img data-name="Exploding Head" data-slug="exploding_head" data-position="3" src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18" /> <img data-name="Raised Hands" data-slug="raised_hands" data-position="4" src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18" /> <img data-name="Fire" data-slug="fire" data-position="5" src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18" /> </div> </body> </html>