<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrew Betts</title>
    <description>The latest articles on DEV Community by Andrew Betts (@triblondon).</description>
    <link>https://dev.to/triblondon</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F909783%2F77e76f40-7f25-481c-9d49-11add5fec33c.jpeg</url>
      <title>DEV Community: Andrew Betts</title>
      <link>https://dev.to/triblondon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/triblondon"/>
    <language>en</language>
    <item>
      <title>Build "For you" recommendations using AI on Fastly!</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Wed, 07 Aug 2024 11:48:17 +0000</pubDate>
      <link>https://dev.to/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap</link>
      <guid>https://dev.to/fastly/build-for-you-recommendations-using-ai-on-fastly-5eap</guid>
      <description>&lt;p&gt;&lt;strong&gt;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.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/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"&gt;&lt;img src="https://media2.dev.to/dynamic/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" width="800" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://www.fastly.com/documentation/solutions/use-cases/personalization/" rel="noopener noreferrer"&gt;personalization topic on our developer hub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But what if you want to go further and generate the personalisation data &lt;strong&gt;at the edge&lt;/strong&gt;?  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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "For you" use case
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Sounds like we can generate recommendations at the edge!&lt;/p&gt;

&lt;h2&gt;
  
  
  A real world example
&lt;/h2&gt;

&lt;p&gt;Let's take a look at the website of the &lt;a href="https://www.metmuseum.org/" rel="noopener noreferrer"&gt;New York Metropolitan Museum of Art&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/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"&gt;&lt;img src="https://media2.dev.to/dynamic/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" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/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"&gt;&lt;img src="https://media2.dev.to/dynamic/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" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This seems to use a fairly straightforward system of &lt;a href="https://www.nngroup.com/articles/filters-vs-facets/" rel="noopener noreferrer"&gt;faceting&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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?&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding personalized recommendations
&lt;/h2&gt;

&lt;p&gt;There's lots of ways we can do this, but I wanted to try using a language model, since AI is &lt;em&gt;happening&lt;/em&gt; right now, and it's really different from the way the Met's existing related artworks mechanism seems to work.  Here's the plan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download &lt;a href="https://www.metmuseum.org/about-the-met/policies-and-documents/open-access#get-started-header" rel="noopener noreferrer"&gt;the Met's open access collection dataset&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Run it through a language model to create &lt;a href="https://www.pinecone.io/learn/vector-embeddings/" rel="noopener noreferrer"&gt;vector embeddings&lt;/a&gt; – lists of numbers suitable for machine learning tasks.&lt;/li&gt;
&lt;li&gt;Build a performant similarity search engine for the resulting half a million vectors (representing the Met’s artworks) and load it into &lt;a href="https://www.fastly.com/products/kv-store" rel="noopener noreferrer"&gt;KV store&lt;/a&gt; so we can use it from &lt;a href="https://www.fastly.com/products/edge-compute" rel="noopener noreferrer"&gt;Fastly Compute&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once we've done all that, we should be able to, as you browse the Met's website:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Track the artworks you visit in a cookie.&lt;/li&gt;
&lt;li&gt;Look up the vectors corresponding to those artworks.&lt;/li&gt;
&lt;li&gt;Calculate an average vector representing your browsing interests.&lt;/li&gt;
&lt;li&gt;Plug that into our similarity search engine to find the most similar artworks.&lt;/li&gt;
&lt;li&gt;Load details about those artworks from the &lt;a href="https://metmuseum.github.io/#object" rel="noopener noreferrer"&gt;Met's Object API&lt;/a&gt; and augment the page with personalized recommendations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Et voilà, personalized recommendations:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/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"&gt;&lt;img src="https://media2.dev.to/dynamic/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" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OK, so let's break that down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the dataset
&lt;/h2&gt;

&lt;p&gt;The Met's raw dataset is a CSV with lots of columns and looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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",,,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple enough to &lt;a href="https://github.com/fastly/edgeml-recommender/blob/main/scripts/preprocess.py" rel="noopener noreferrer"&gt;transform that into two columns&lt;/a&gt;, an ID and a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use the &lt;a href="https://pypi.org/project/transformers/" rel="noopener noreferrer"&gt;transformers&lt;/a&gt; package from &lt;a href="https://huggingface.co/" rel="noopener noreferrer"&gt;Hugging Face AI toolset&lt;/a&gt;, and generate embeddings of each of these descriptions.  We &lt;a href="https://github.com/fastly/edgeml-recommender/blob/main/scripts/create-embeddings.py" rel="noopener noreferrer"&gt;used&lt;/a&gt; the &lt;code&gt;sentence-transformers/all-MiniLM-L12-v2&lt;/code&gt; model, and used &lt;a href="https://www.ibm.com/topics/principal-component-analysis" rel="noopener noreferrer"&gt;principal component analysis&lt;/a&gt; (PCA) to reduce the resulting vectors to 5 dimensions.  That gives you something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"vector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-0.005544120445847511&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-0.030924081802368164&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.008597176522016525&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.20186401903629303&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0578165128827095&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"vector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-0.005544120445847511&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-0.030924081802368164&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.008597176522016525&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.20186401903629303&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0578165128827095&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So, let's &lt;a href="https://github.com/fastly/edgeml-recommender/blob/main/scripts/partition.py" rel="noopener noreferrer"&gt;partition the data&lt;/a&gt;.  We can use &lt;a href="https://en.wikipedia.org/wiki/K-means_clustering" rel="noopener noreferrer"&gt;KMeans&lt;/a&gt; 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:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/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"&gt;&lt;img src="https://media2.dev.to/dynamic/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" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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! &lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world" rel="noopener noreferrer"&gt;Hierarchical Navigable Small Worlds&lt;/a&gt; (HNSW), and it has a &lt;a href="https://github.com/instant-labs/instant-distance" rel="noopener noreferrer"&gt;pure Rust implementation&lt;/a&gt;, which we're using to write our edge app.  So we wrote a &lt;a href="https://github.com/fastly/edgeml-recommender/blob/main/precompiler/src/main.rs" rel="noopener noreferrer"&gt;small standalone Rust app&lt;/a&gt; to construct the HNSW graph structs for each dataset, and then used &lt;a href="https://crates.io/crates/bincode" rel="noopener noreferrer"&gt;bincode&lt;/a&gt; to export the memory of the instantiated struct into a binary blob.&lt;/p&gt;

&lt;p&gt;Now, those binary blobs can be loaded into &lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/data-stores/" rel="noopener noreferrer"&gt;KV store&lt;/a&gt;, keyed against the cluster index, and the cluster index can be included in our edge app. &lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the edge app
&lt;/h2&gt;

&lt;p&gt;The application that we run at the edge needs to handle several types of requests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTML pages:&lt;/strong&gt; We fetch these from &lt;code&gt;metmuseum.org&lt;/code&gt; and transform the response to add extra front-end &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags, so we can inject a bit of our own front end processing and content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Fastly script and style resources&lt;/strong&gt; referenced by those extra tags, which we can serve directly out of the edge app's binary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The recommender endpoint&lt;/strong&gt;, which generates and returns the recommendations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All other (non-HTML) requests:&lt;/strong&gt; Images, and the Met's own scripts and stylesheets, which we proxy directly from their domain without alteration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We initially &lt;a href="https://github.com/fastly/edgeml-recommender/blob/main/met-example/src/index.js" rel="noopener noreferrer"&gt;built this app in JavaScript&lt;/a&gt;, but ended up &lt;a href="https://github.com/fastly/edgeml-recommender/blob/main/recommender/src/main.rs" rel="noopener noreferrer"&gt;porting the recommender part to Rust&lt;/a&gt; because we liked the HNSW implementation in &lt;a href="https://docs.rs/instant-distance" rel="noopener noreferrer"&gt;instant-distance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/fastly/edgeml-recommender/blob/main/met-example/src/assets/client.js" rel="noopener noreferrer"&gt;client side JavaScript&lt;/a&gt; does a few interesting things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver" rel="noopener noreferrer"&gt;IntersectionObserver&lt;/a&gt;, 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 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scroll_event" rel="noopener noreferrer"&gt;onscroll&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Make a fetch to our special recommendations API endpoint (which we can then handle at the edge and return object information)&lt;/li&gt;
&lt;li&gt;Compose some HTML using a template built into a client-side function&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Let us know what you build &lt;a href="https://community.fastly.com/t/insert-ai-generated-personalized-for-you-content-at-the-edge-why-not/2855" rel="noopener noreferrer"&gt;on our community thread&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>fastly</category>
      <category>rust</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The elusive perfect language switcher</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Tue, 23 Jul 2024 15:29:53 +0000</pubDate>
      <link>https://dev.to/fastly/the-elusive-perfect-language-switcher-2mp9</link>
      <guid>https://dev.to/fastly/the-elusive-perfect-language-switcher-2mp9</guid>
      <description>&lt;p&gt;&lt;strong&gt;Lots of websites get tripped up trying to offer users a choice of language. But a few simple steps can ensure your users get exactly the language they want.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I have the dubious honor in my social circle of being one of the few who speaks only one language fluently. Fortunately for me the language I speak is a pretty popular one, but when I visit websites when I'm overseas, even ones I use a lot, it's surprising how often they suddenly think I want the site in Spanish, Japanese or Polish. This is one of a bunch of things websites often get wrong when designing a language-localized experience.&lt;/p&gt;

&lt;p&gt;We can group these language-switching fails into roughly three categories: bad assumptions, poor labeling, and unintuitive persistence. Let's address these and then consider a checklist of good practices for language switchers (and also some ideas for how to use edge computing to make this easier!).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bad assumptions:&lt;/strong&gt; the most common mistakes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poor labeling:&lt;/strong&gt; avoid red flags!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken persistence:&lt;/strong&gt; what not to forget&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge computing to the rescue!&lt;/strong&gt; (so predictable but I like my job etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bad assumptions
&lt;/h2&gt;

&lt;p&gt;A user's physical location is not the best indicator of the language they understand. It's correlated: it's certainly true that a user based in Mexico is much more likely to speak Spanish than a user in Finland. But this logic results in a really poor experience when people are traveling away from their home country. Using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language" rel="noopener noreferrer"&gt;&lt;code&gt;Accept-Language&lt;/code&gt;&lt;/a&gt; header (which communicates the user's device system locale) is a much better way to figure out what language to select.&lt;/p&gt;

&lt;p&gt;For retailers, it's also risky to assume that delivery location, language and currency should all change together.  If I want to buy a gift for a friend overseas, I might want a foreign delivery location but still shop in my native language and currency.&lt;/p&gt;

&lt;p&gt;What if someone migrates to a new country but still prefers their native language? Now you might have a currency and location that seem to match but with a language that doesn't fit. The more flexibility you can build into your system design, the less convoluted the solutions you'll have to come up with to deal with these edge cases. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.google.com/flights" rel="noopener noreferrer"&gt;Google Flights&lt;/a&gt; offers a masterclass in doing this correctly - language preference is detected from browser locale, location from your IP address, and currency synced to location - but all are independently adjustable:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gcpp75r4160a7imkhnw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gcpp75r4160a7imkhnw.png" alt="Google flights locale selectors" width="800" height="62"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In summary:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't assume that the user wants the predominant language of the country they are physically located in. Use browser locale in preference to the user's physical location when choosing a language automatically.&lt;/li&gt;
&lt;li&gt;Don't assume that currency, language and delivery location are all "in sync"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Poor labeling
&lt;/h2&gt;

&lt;p&gt;Flags are a somewhat controversial way of representing language choice. Neilsen, a UX consultancy, found that &lt;a href="https://www.nngroup.com/articles/language-switching-ecommerce/" rel="noopener noreferrer"&gt;users do associate their country's flag with their language&lt;/a&gt;, but where a language is spoken in multiple countries, this can be quite a political minefield.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F85nltbb2r9xbnw0cqyoa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F85nltbb2r9xbnw0cqyoa.png" alt="Bizarrely labeled language switcher" width="598" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;My favorite example of a bizarrely labeled language switcher from a German sofa company.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's not completely wrong to associate languages with places.  The English spoken in the UK is clearly not (entirely) the same as that spoken in the US, and the commonly used &lt;a href="https://en.wikipedia.org/wiki/IETF_language_tag" rel="noopener noreferrer"&gt;IETF language tags&lt;/a&gt; standard uses this as the basis for classifying language variants: &lt;code&gt;en-GB&lt;/code&gt;, and &lt;code&gt;en-US&lt;/code&gt;, for example.&lt;/p&gt;

&lt;p&gt;As with so many standards that attempt to classify and group human behavior, some compromise is needed. Most correctly you might say that &lt;code&gt;en-IN&lt;/code&gt; is "a dialect of the English language which has its cultural center of gravity in India" but how exactly you define that dialect and who gets to do so is highly debatable, and obviously any variant of any language could be spoken in any physical location in the world.&lt;/p&gt;

&lt;p&gt;So, using flags can be a nice experience for users but is hard to make work across a large user population unless you have invested in multiple variants.&lt;/p&gt;

&lt;p&gt;Businesses clearly need to be pragmatic, and will invest time and money only where there is a market for their products or services. It's actually quite uncommon for websites (except the very largest) to bother providing services in multiple variants of the same language, and where there is a link between locations and languages, it's often more likely to be because the business has a local version of the entire site, which is then offered in whatever languages are deemed to be relevant to that location.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.booking.com/" rel="noopener noreferrer"&gt;Booking.com&lt;/a&gt; doesn't have regional sites, and uses flags for language choice, but is able to offer the correct regional variants of the languages that match the flags (check out the multiple English, Spanish and Portuguese variants here):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferix5213e2dvufb5qojs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferix5213e2dvufb5qojs.png" alt="Booking.com language switcher" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great job Booking.com team!&lt;/p&gt;

&lt;p&gt;Now consider &lt;a href="https://www.ikea.com" rel="noopener noreferrer"&gt;IKEA&lt;/a&gt;, which unlike Booking.com is selling physical products that vary between countries, and therefore they do have regional sites each offered in a different selection of languages.  &lt;/p&gt;

&lt;p&gt;IKEA Portugal, for example, offers Portuguese and English, but doesn't specify which variant of either of those they are using ('pt/en' doesn't mean "the variant of English spoken in Portugal", but rather "the Portugal website, rendered in English"):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxkq0kkesnzjjil88ig0v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxkq0kkesnzjjil88ig0v.png" alt="Address bar showing ikea.com/pt/en/shoppingcart" width="800" height="173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's a pretty common pattern for sites whose product offering varies between countries. Here's Storytel's Brazil site, which is only available in Portuguese (presumably &lt;code&gt;pt-BR&lt;/code&gt;?):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wrcbjl9nctshkg8h4dz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wrcbjl9nctshkg8h4dz.png" alt="Address bar showing storytel.com/br/pt" width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In summary, disassociating language and location is pretty hard and ultimately the most practical solution depends on to what extent your product or service is different in different markets.&lt;/p&gt;

&lt;p&gt;The other labeling issue I find interesting is the icon or symbol used to indicate that language selection is available. The most common choices are a globe, some kind of specific "languages" icon, the name of the currently selected language, or a list of available languages with the current one highlighted.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://languageicon.org/" rel="noopener noreferrer"&gt;Language Icon project&lt;/a&gt; has been trying to standardize this for 15 years, and today doesn't enjoy very much traction, while most major global sites seem to have adopted a globe.  The advantage of the big ol' list of languages approach (normally in the footer) is that if I see a wall of text I don't understand, I can press CMD+F and search the page for the word "English" and find the link to the English version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F556gq8h8rnhxzfrk1x5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F556gq8h8rnhxzfrk1x5r.png" alt="Wikipedia language options" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The worst offense here is to have a menu option somewhere called "Change language". Any English speaker who has ever been pranked by friends who changed their phone's UI to Korean will be familiar with the challenges this presents.&lt;/p&gt;

&lt;p&gt;Labeling is really important. Some things to bear in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write the names of languages in the language itself.  Offer "Deutsch", not "German"&lt;/li&gt;
&lt;li&gt;Don't expose users to labels designed for computers.  No-one should have to know what "DE" means.&lt;/li&gt;
&lt;li&gt;Make clear the distinction between "service location" and "language region"&lt;/li&gt;
&lt;li&gt;Use a globe symbol for a language selector&lt;/li&gt;
&lt;li&gt;Consider listing available languages somewhere on the page&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Unintuitive persistence
&lt;/h2&gt;

&lt;p&gt;The final horseman of the language apocalypse is the challenge of persisting language choices, and when to do so.  Users have an annoying tendency to log out, use more than one device, and even send links to friends and family who might prefer different languages!&lt;/p&gt;

&lt;p&gt;A great example of this problem happens if you are visiting a friend overseas and they send you a link to a Google Maps location where they want to meet you. Odds are, you'll click that link and it will display Google Maps in your friend's language, not yours. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsssjqwhsl1l7worgj3bv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsssjqwhsl1l7worgj3bv.png" alt="Google maps UI in Japanese" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the face of it that seems odd because in principle the way the web works is that the same URL should be available in multiple languages and the server gives you the one that matches your &lt;code&gt;Accept-Language&lt;/code&gt; preference. However, this idealized idea of '&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation" rel="noopener noreferrer"&gt;content negotiation&lt;/a&gt;' is rarely used in its purest form on the web, not just because of technical headaches but because it sometimes isn't actually the right experience.&lt;/p&gt;

&lt;p&gt;On a news / long-form content site, if content is available in multiple languages, it's rarely exactly equivalent.  I might send the French version of an article to a friend who normally prefers English, because it reads better in French, or includes content not available in the English version.&lt;/p&gt;

&lt;p&gt;Sharing is a minefield.  But problems with persistence can arise even when just dealing with one user.  What if a user registers for an account on your site, and has never made an explicit language selection, but then changes their device language. Since they have an account, you might feel that in this situation the account settings have taken over from automatic selection, but since they never made an explicit choice, shouldn't we still be led by their browser locale?&lt;/p&gt;

&lt;p&gt;This is where we need to make smart decisions about how to persist language choice, and the hierarchy of precedence for the different language signals.  I prefer the following principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "user preference" is an explicit choice saved on your account, or the &lt;code&gt;Accept-Language&lt;/code&gt; header if there's no saved preference.&lt;/li&gt;
&lt;li&gt;A language specific URL overrides the user preference, but an inconsistency between the two should be signalled to the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if a friend sends me a link to &lt;code&gt;www.example.com&lt;/code&gt; and I've never been there before, my friend might have seen it in French but I'd get it in English, because of &lt;code&gt;Accept-Language&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, if they sent me a link to &lt;code&gt;www.example.com/fr/products/123&lt;/code&gt;, then I'd see the page in French, with an alert asking me (in English) if I want to switch to English.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge computing to the rescue
&lt;/h2&gt;

&lt;p&gt;I'm obviously now going to tell you how Fastly is the answer to all your language challenges, right?  Well, it does actually make a lot of sense to manage aspects of language selection at the edge.  One of the most important things to do is to normalize the &lt;code&gt;Accept-Language&lt;/code&gt; value.  In &lt;a href="https://www.fastly.com/documentation/guides/vcl/using/" rel="noopener noreferrer"&gt;Fastly VCL&lt;/a&gt; you can do that like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;req.http.Accept-Language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; accept.language_lookup&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"en:de:fr:nl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"de"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;req.http.Accept-Language&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will turn a value such as &lt;code&gt;en-GB,en-US;q=0.9,en;q=0.8&lt;/code&gt; into &lt;code&gt;en&lt;/code&gt;, and means that you can store far fewer versions of your pages.&lt;/p&gt;

&lt;p&gt;Also consider redirecting users to a language-specific URL when they arrive at your homepage. A &lt;a href="https://www.fastly.com/documentation/reference/vcl/subroutines/#custom-subroutines" rel="noopener noreferrer"&gt;custom VCL subroutine&lt;/a&gt; will do the trick here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;sub&lt;/span&gt; &lt;span class="nf"&gt;locale_redirect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;declare&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;var.urlLocale&lt;/span&gt; &lt;span class="k"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;declare&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;var.prefLocale&lt;/span&gt; &lt;span class="k"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;var.urlLocale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;req.url.path &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s2"&gt;"^/(en|de|fr|nl)/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; re.group.1&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;var.prefLocale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;req.http.cookie&lt;/span&gt;:lang&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nv"&gt;req.http.cookie&lt;/span&gt;:lang&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nv"&gt;req.http.Accept-Language&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;var.urlLocale&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;var.prefLocale&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    error &lt;span class="mi"&gt;600&lt;/span&gt; &lt;span class="nv"&gt;var.prefLocale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whatever you do at the edge, I hope some of this helps you build more insightful, more intuitive language-sensitive experiences for your users!&lt;/p&gt;

&lt;p&gt;Also tell us what you build, at &lt;a href="https://community.fastly.com" rel="noopener noreferrer"&gt;community.fastly.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>localization</category>
      <category>a11y</category>
      <category>fastly</category>
      <category>ux</category>
    </item>
    <item>
      <title>Is purging still the hardest problem in computer science?</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Wed, 01 May 2024 08:37:44 +0000</pubDate>
      <link>https://dev.to/fastly/is-purging-still-the-hardest-problem-in-computer-science-339i</link>
      <guid>https://dev.to/fastly/is-purging-still-the-hardest-problem-in-computer-science-339i</guid>
      <description>&lt;p&gt;One of the most common reasons for customers sending support tickets to Fastly is for help with &lt;em&gt;purging content from cache&lt;/em&gt; - either it stays too long, or doesn't stay long enough. We have the &lt;strong&gt;best purging mechanism of any edge network&lt;/strong&gt;, so what's going on?&lt;/p&gt;

&lt;p&gt;It's said there are &lt;a href="https://martinfowler.com/bliki/TwoHardThings.html" rel="noopener noreferrer"&gt;only two hard problems in computer science&lt;/a&gt;: cache invalidation, naming things and off-by-one errors.  Caching stuff close to where your users are is just one of many things you can do with Fastly these days, but it's still one of the easiest and most effective ways to make your website faster, more reliable and more engaging. Traditionally, you just set a TTL (time to live) and let the cache just hold onto it for that amount of time. &lt;/p&gt;

&lt;p&gt;However, while serving outdated content might have been acceptable in the past it just doesn't cut the mustard today. News editors need stories to be up to date. Prices in stores must be correct and consistent. Application versions must be compatible. Purging the right things quickly is essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three types of purge
&lt;/h2&gt;

&lt;p&gt;How do you purge the right set of things?  We offer three flavors of purging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single item / URL purge&lt;/li&gt;
&lt;li&gt;Purge all&lt;/li&gt;
&lt;li&gt;Surrogate key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the off chance that you just want to purge one single item by its URL, that's a &lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/#url-purge" rel="noopener noreferrer"&gt;URL purge&lt;/a&gt; and on Fastly you do it by sending an HTTP &lt;code&gt;PURGE&lt;/code&gt; request to the very URL you want to purge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PURGE &lt;span class="s2"&gt;"https://www.example.com/hot-deals"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/#url-purge" rel="noopener noreferrer"&gt;&lt;strong&gt;URL purges&lt;/strong&gt;&lt;/a&gt; are simple and easy, but affecting just a single URL is not really very scalable, and if that URL is ever requested with a query string like &lt;code&gt;?page=2&lt;/code&gt;, that's a different URL! Sorry, you will have to purge that and every other variant as well.&lt;/p&gt;

&lt;p&gt;The natural complement to the single purge, then, is the &lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/#purge-all" rel="noopener noreferrer"&gt;&lt;strong&gt;purge all&lt;/strong&gt;&lt;/a&gt;.  Very much for the "nuke it from orbit" crowd, this blunt instrument has no equal.  A quick call to a fixed API endpoint and your entire service's cache is wiped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.fastly.com/service/{SERVICE_ID}/purge_all"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Fastly-Key: {YOUR_FASTLY_API_TOKEN}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most cases are more nuanced than either of these scenarios. Purge anything that mentions product 36253; purge all images; purge all URLs under the &lt;code&gt;/products/shirts&lt;/code&gt; prefix; purge all the 720p variants from all season 2 episodes of show 414562; purge all the resources associated with the "about us" page… the list of possible purge criteria is endless.  For all your precision purging needs, look no further than &lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/#surrogate-key-purge" rel="noopener noreferrer"&gt;&lt;strong&gt;surrogate keys&lt;/strong&gt;&lt;/a&gt;, which let you apply tags to content and then purge everything that has a particular tag.&lt;/p&gt;

&lt;p&gt;For example, you could serve a response from your origin server with the following header listing out all the tags you want to put on that response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Surrogate-Key: html product-123 product-456 region-eu /products /products/shirts /products/shirts/123
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, later, send a purge for one of the tags you put on that response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.fastly.com/service/{SERVICE_ID}/purge/product-123"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Fastly-Key: {YOUR_FASTLY_API_TOKEN}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will surgically remove all the cached content that has that tag, without affecting the rest of your cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fastest purge... in the world.
&lt;/h2&gt;

&lt;p&gt;Fastly purges your stuff really fast.  When you send us a URL or surrogate key purge, it will be received by the &lt;a href="https://www.fastly.com/documentation/guides/concepts/pop/" rel="noopener noreferrer"&gt;Fastly POP&lt;/a&gt; (point-of-presence) closest to you, and then from there it is replicated rapidly and efficiently to every other POP in the world. Copies of your objects will &lt;strong&gt;start to purge within 5ms&lt;/strong&gt; and every Fastly POP in the world should be done in around 150ms.  It's almost always complete within 250ms.&lt;/p&gt;

&lt;p&gt;That's the fastest purge of any edge network or CDN (by quite a margin). The laws of physics give us a theoretical fastest purge time of about 65ms, the time it takes light to travel (one way) from one side of the planet to the other, but we also need to account for some processing time, buffering and retransmit delays, and the fact that cables do not go in completely straight lines, so 150ms is about the fastest you can ever expect a global purge to be.&lt;/p&gt;

&lt;p&gt;(Note that "purge all" invokes a completely different mechanism and takes up to 1 minute to complete)&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache more stuff!
&lt;/h2&gt;

&lt;p&gt;When you can purge things in 150ms, you can cache more, and not worry about it. Historically it was risky to cache content on a CDN if it might change, or if it contained anything that was specific to the user browsing the site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some things never change
&lt;/h3&gt;

&lt;p&gt;Content that never changes is great for caching, obviously. And you can help create more of that by using &lt;strong&gt;immutable URLs&lt;/strong&gt; for all your assets. These contain a hash of the content of the file, like &lt;code&gt;/scripts/main.142c04bb.min.js&lt;/code&gt;, and should be served with a cache policy such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cache-Control: public, max-age=31536000, immutable
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Web frameworks like &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; will usually include this feature, but do check that they set the caching headers correctly!&lt;/p&gt;

&lt;p&gt;For content that changes constantly or is otherwise genuinely uncacheable, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cache-Control: private, no-store
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But is it uncacheable, really?&lt;/p&gt;

&lt;h3&gt;
  
  
  Event driven content
&lt;/h3&gt;

&lt;p&gt;What about content that doesn't change often but when it does, it needs to update immediately? Things like news articles - which typically remain static after publishing but may need an urgent update to correct an error? &lt;/p&gt;

&lt;p&gt;We call this &lt;strong&gt;event-driven content&lt;/strong&gt;.  For this use case, you'll want to configure caching like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cache-Control: public, max-age=3600
Surrogate-Control: public, max-age=31536000
Surrogate-Key: content article-12345
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Fastly to cache the content "forever" (for a year) but the browser should only cache for a short period (1 hour in this example).  We also tag the content with any relevant surrogate key tags that will help you target it when you want to purge.  When the article is updated, send a URL purge or a surrogate key purge.  If you are using a &lt;a href="https://www.fastly.com/documentation/guides/integrations/plugins/" rel="noopener noreferrer"&gt;Fastly plugin in your CMS&lt;/a&gt;, it might do this for you automatically!&lt;/p&gt;

&lt;h3&gt;
  
  
  Private or authenticated content
&lt;/h3&gt;

&lt;p&gt;What if the content changes when the user logs in, or is subtly different depending on the user's privileges, memberships or subscriptions? If there are a lot of possible variations, consider personalizing the content at the edge, but if there's only a few, you can &lt;a href="https://www.fastly.com/blog/getting-most-out-vary-fastly" rel="noopener noreferrer"&gt;use the &lt;strong&gt;Vary&lt;/strong&gt; header&lt;/a&gt;!  Start by normalizing the authentication state on the request before it is forwarded to the origin (e.g. by &lt;a href="https://www.fastly.com/documentation/solutions/examples/json-web-tokens/" rel="noopener noreferrer"&gt;decoding a JSON web token session cookie&lt;/a&gt;).  You can then add headers to the request such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;User-role: admin
User-region: europe
User-is-subscriber: true
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then your response content can be marked up with headers such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cache-Control: private, no-store
Surrogate-Control: public, max-age=31536000
Surrogate-Key: content article-12345
Vary: User-is-subscriber, User-region
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well if the number of permutations of the request headers you are varying on is small. We allow up to 50 variations per object (in each POP) in our VCL services and have &lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/cache/#limitations-and-constraints" rel="noopener noreferrer"&gt;slightly different rules for Compute services&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Purge power moves
&lt;/h2&gt;

&lt;p&gt;So you can often cache more than you think you can, and using cache at the edge is one of the most effective ways to improve end-user experiences and reduce costs.  Customers often come up with really interesting patterns for using purging - here are some to inspire you!&lt;/p&gt;

&lt;h3&gt;
  
  
  Soft purge and serving stale
&lt;/h3&gt;

&lt;p&gt;Generally when you purge things you expect them to die immediately, the clue being very much in the word "purge". But there are a couple of scenarios where maybe we can do something better. What about things that are super popular, being requested thousands of times a minute, or even per second?  When you purge things like that, it can create a poor experience for not just one user but everyone who wants that resource before we've managed to re-cache it.&lt;/p&gt;

&lt;p&gt;Or what about when your origin servers go down?  Some system could unhelpfully purge content at a bad moment when the origin is not available to serve a new version. &lt;/p&gt;

&lt;p&gt;Both of these scenarios can be improved by supporting &lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/cache/stale/" rel="noopener noreferrer"&gt;stale serving&lt;/a&gt;.  Add the &lt;code&gt;stale-while-revalidate&lt;/code&gt; and &lt;code&gt;stale-if-error&lt;/code&gt; directives to your &lt;code&gt;Cache-Control&lt;/code&gt; header to activate this behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cache-Control: public, max-age=31536000, stale-while-revalidate=60, stale-if-error=31536000
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when issuing your purge, set the &lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/#soft-vs-hard-purging" rel="noopener noreferrer"&gt;soft purge flag&lt;/a&gt;, and instead of purging the object, we'll instead &lt;strong&gt;mark it as stale&lt;/strong&gt;.  This is a real superpower and frankly unless you have a good reason not to, you should consider doing this in pretty much every scenario where you purge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Path prefix purging
&lt;/h3&gt;

&lt;p&gt;Want to purge everything under &lt;code&gt;/products/&lt;/code&gt;? Use surrogate keys to &lt;a href="https://www.fastly.com/documentation/solutions/examples/purge-everything-under-a-url-path-prefix/" rel="noopener noreferrer"&gt;tag your responses with every path segment prefix&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Surrogate-Key: /products/winter-season/shirts/378245 /products/winter-season/shirts /products/winter-season /products
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now issue a surrogate key purge for "/products" or "/products/winter-season" and we'll delete everything with those prefixes (in 150ms!)&lt;/p&gt;

&lt;h3&gt;
  
  
  Slow purges
&lt;/h3&gt;

&lt;p&gt;Sometimes you want to purge a lot of content, but you don't want it to all disappear at once, because that will cause unreasonably high load on your origin servers. &lt;a href="https://yann.mandragor.org/posts/purge-group-pattern/" rel="noopener noreferrer"&gt;Yann Harmon from Contentful has a great solution to this&lt;/a&gt;, adding a random-number surrogate key tag (from a set of 100) to every response. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Surrogate-Key: purge-group-23
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now issue purges for all 100 purge group tags (&lt;code&gt;purge-group-1&lt;/code&gt;, &lt;code&gt;purge-group-2&lt;/code&gt;, &lt;code&gt;purge-group-3&lt;/code&gt;, etc), with whatever pause you want in between each one, to draw out the purge over a longer period of time, and give content time to re-cache. It's unusual to have customers want to purge more slowly but sometimes it's useful!&lt;/p&gt;

&lt;h3&gt;
  
  
  Calculated TTLs at the edge
&lt;/h3&gt;

&lt;p&gt;It's generally best to determine cache policy at the point that content is generated, typically on your origin servers, and write the instructions for Fastly into headers like Cache-Control and Surrogate-Control. But you can also &lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/cache/cache-freshness/#overriding-semantics" rel="noopener noreferrer"&gt;set the TTL of content within the logic of your Fastly service&lt;/a&gt; if you like.&lt;/p&gt;

&lt;p&gt;This can allow you to do almost anything, for example one customer decided to have their cache reset once a day at a predetermined time, but didn't want to rely on issuing a purge on a schedule, so instead wrote logic to &lt;a href="https://www.fastly.com/documentation/solutions/examples/scheduled-invalidation/" rel="noopener noreferrer"&gt;adjust the TTL of each response so that they always expire at the next reset point&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combating race conditions
&lt;/h3&gt;

&lt;p&gt;Sometimes it seems like a purge didn't work, and that's typically because it happened too early. If you purge Fastly before the upstream server has updated, we might just pull the old version of the content and re-cache it.  Take care to ensure that your origin is serving the new version before you purge Fastly.&lt;/p&gt;

&lt;p&gt;This also often happens when you're using our &lt;a href="https://www.fastly.com/documentation/guides/concepts/shielding/" rel="noopener noreferrer"&gt;shielding&lt;/a&gt; feature, which puts two layers of Fastly between the end user and your origin.  If your purge is processed by other Fastly POPs before it reaches the shield POP, the purged POPs might re-cache from the shield cache.  This is typically only a problem with &lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/#purge-all" rel="noopener noreferrer"&gt;purge all&lt;/a&gt;, because it takes significantly longer than a URL purge or a surrogate key purge.&lt;/p&gt;

&lt;p&gt;The way a "purge all" works actually provides a solution to this problem because this kind of purge is done by increasing the cache version number, and you can read the current version number from the configuration of your Fastly service. By &lt;a href="https://www.fastly.com/documentation/solutions/examples/prevent-race-conditions-with-purge-all-and-shielding/" rel="noopener noreferrer"&gt;comparing the version number of the upstream cache with the version of the local cache&lt;/a&gt;, it's possible to identify when a purge is in progress and responses should not yet be cached.&lt;/p&gt;

&lt;p&gt;Or you could, you know, just purge twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Edge networks can now do incredibly smart stuff, and at Fastly we spent a lot of time building more and more features you can make use of to improve the security, performance and scalability of your apps and websites.  But caching is still one of the most powerful things you can do, and something you should spend time getting right. It can have dramatic effects on your costs and environmental impact too.&lt;/p&gt;

&lt;p&gt;Taking advantage of the speed and precision of Fastly's purging machinery allows you to get the maximum value out of edge caching.  Tell us how you're doing it at &lt;a href="http://community.fastly.com" rel="noopener noreferrer"&gt;community.fastly.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>caching</category>
      <category>fastly</category>
      <category>performance</category>
      <category>devops</category>
    </item>
    <item>
      <title>11 things I do in every Fastly service I create</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Tue, 26 Mar 2024 11:46:30 +0000</pubDate>
      <link>https://dev.to/fastly/11-things-i-do-in-every-fastly-service-i-create-4epn</link>
      <guid>https://dev.to/fastly/11-things-i-do-in-every-fastly-service-i-create-4epn</guid>
      <description>&lt;p&gt;&lt;strong&gt;The variety of things you can do in edge computing these days is huge, and Fastly customers are constantly impressing me with their creativity and the complexity of some of the problems that can be solved entirely at the edge.  But there are some things that almost regardless of your use case, you probably should be doing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've set up a lot of Fastly services, and along the way I've realized that pretty much every time, I add the same patterns to do the same useful stuff.  Whether it's an e-commerce site, something statically generated, a SaaS service, or anything else, there's a set of stuff that is almost universally applicable.  Let's check them off here, with examples for Fastly's &lt;a href="https://www.fastly.com/documentation/guides/vcl/" rel="noopener noreferrer"&gt;Delivery (VCL) services&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(If JavaScript, Rust or Go are more your thing, try &lt;a href="https://www.fastly.com/products/compute" rel="noopener noreferrer"&gt;Fastly Compute services&lt;/a&gt;. It's free to sign up and you can do a lot of the same things I've listed here)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Shielding
&lt;/h2&gt;

&lt;p&gt;If your Fastly service uses &lt;a href="https://www.fastly.com/documentation/guides/integrations/backends/" rel="noopener noreferrer"&gt;backends&lt;/a&gt;, i.e. you have origin servers behind Fastly, it is almost always a good idea to enable &lt;a href="https://www.fastly.com/documentation/guides/concepts/shielding/" rel="noopener noreferrer"&gt;shielding&lt;/a&gt;, which focuses "miss" traffic into one Fastly &lt;a href="https://www.fastly.com/documentation/guides/concepts/pop/" rel="noopener noreferrer"&gt;POP&lt;/a&gt; before sending it to your origin.  You then have two chances to get a hit in the cache, and the cache in the shield POP will typically become larger and more comprehensive than other POPs, so this can reduce traffic to your origin servers by a huge amount: up to two orders of magnitude!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftclfvkhw1eq591aigeas.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftclfvkhw1eq591aigeas.gif" alt="Shielding UI" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shielding needs to be enabled as a property of the backend configuration, either in the control panel or when creating a backend &lt;a href="https://www.fastly.com/documentation/reference/api/services/backend/" rel="noopener noreferrer"&gt;via the Fastly API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Want to know where to shield?  Use the &lt;a href="https://www.fastly.com/documentation/guides/concepts/shielding/#choosing-a-shield-location" rel="noopener noreferrer"&gt;shield chooser tool&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP/3
&lt;/h2&gt;

&lt;p&gt;Although Fastly negotiates HTTP/2 automatically if your &lt;a href="https://www.fastly.com/documentation/guides/concepts/routing-traffic-to-fastly/#use-a-tls-configuration" rel="noopener noreferrer"&gt;TLS configuration&lt;/a&gt; supports it, at time of writing we require you to explicitly enable H3 by adding an &lt;code&gt;Alt-Svc&lt;/code&gt; header to your responses - but the good news is that you can enable this in the control panel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesexa25ees4fsy59jduf.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesexa25ees4fsy59jduf.gif" alt="H3 UI" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or drop a single line of VCL into &lt;code&gt;vcl_recv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;h3.alt_svc&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Force TLS
&lt;/h2&gt;

&lt;p&gt;Another quick win is forcing all clients to use TLS. We do accept connections on plain HTTP in VCL services, so it's a good idea to redirect them to HTTPS. This can be done using a toggle in the control panel, right next to the HTTP/33 option.&lt;/p&gt;

&lt;p&gt;Or use custom VCL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;req.protocol &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"https"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  error &lt;span class="mi"&gt;608&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We recommend throwing error codes in the &lt;code&gt;6xx&lt;/code&gt; range and then mapping them to conventional HTTP status codes in &lt;code&gt;vcl_error&lt;/code&gt;, where you can also turn the parameter into a Location header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;obj.status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;608&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;obj.http.location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://"&lt;/span&gt; &lt;span class="nv"&gt;req.http.host&lt;/span&gt; &lt;span class="nv"&gt;req.url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;obj.status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;308&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; obj.response &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Moved permanently"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;deliver&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(by the way, if you're using a Compute service, this redirect happens automatically, and no insecure traffic ever reaches your service)&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP Strict Transport Security
&lt;/h2&gt;

&lt;p&gt;You should also return a &lt;code&gt;strict-transport-security&lt;/code&gt; header to ensure that clients never make an insecure connection.  If you use the control panel to configure Force TLS, HSTS will be set up as well, but if you're using custom VCL, adding response headers just before sending to the client can be done in &lt;code&gt;vcl_deliver&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;set resp.http.strict-transport-security = "max-age=31536000; includeSubDomains; preload"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Compression
&lt;/h2&gt;

&lt;p&gt;The final option that is configurable in the control panel, enable compression under Content &amp;gt; Compression, and we will automatically compress any uncompressed responses from origin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekodvhjpxnym6xreitw0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekodvhjpxnym6xreitw0.gif" alt="Compression UI" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to do this in VCL, take care to update the &lt;code&gt;Vary&lt;/code&gt; header to include "Accept-Encoding":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;beresp.status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.content-type&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s2"&gt;"^(?:text/|application/json)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;req.http.Accept-Encoding&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"br"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; beresp.brotli &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;req.http.Accept-Encoding&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"gzip"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; beresp.gzip &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.Vary&lt;/span&gt;:Accept-Encoding &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CORS
&lt;/h2&gt;

&lt;p&gt;Trying to build handling for &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;Cross-origin resource sharing&lt;/a&gt; into your backend app is often a pain. Instead, handle CORS preflights at the edge and ensure clients get the right CORS rules.  In &lt;code&gt;vcl_recv&lt;/code&gt; you can trap &lt;code&gt;OPTIONS&lt;/code&gt; requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;req.method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"OPTIONS"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;req.http.Origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  error &lt;span class="mi"&gt;612&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in &lt;code&gt;vcl_error&lt;/code&gt;, construct the preflight response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;obj.status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;612&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;obj.status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;obj.http.access-control-allow-origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;req.http.origin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;obj.http.access-control-max-age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"86400"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;deliver&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, in &lt;code&gt;vcl_deliver&lt;/code&gt;, you can add the headers specifying what is allowed, since you need to add these to all responses, not just the preflight ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;resp.http.access-control-allow-methods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GET,HEAD,POST,OPTIONS"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;resp.http.access-control-allow-headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type, x-requested-with"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Redirects
&lt;/h2&gt;

&lt;p&gt;Handling redirection of out of date URLs is a really big and easy win.  Put your redirects in an &lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/dynamic-config/#edge-dictionaries" rel="noopener noreferrer"&gt;edge dictionary&lt;/a&gt;, or if you have pattern-matching redirects, write simple VCL to map the patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;var.dest&lt;/span&gt; &lt;span class="k"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Lookup in table / edge dictionary&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;table.contains&lt;span class="p"&gt;(&lt;/span&gt;my_redirects&lt;span class="p"&gt;,&lt;/span&gt; req.url.path&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;var.dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; table.lookup&lt;span class="p"&gt;(&lt;/span&gt;my_redirects&lt;span class="p"&gt;,&lt;/span&gt; req.url.path&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Special case pattern&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;req.url.path &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s2"&gt;"^/products/(\w+)/([0-9]+)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;var.dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/catalog/categories/"&lt;/span&gt; re.group.1 &lt;span class="s2"&gt;"/products/"&lt;/span&gt; re.group.2&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;var.dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 error &lt;span class="mi"&gt;601&lt;/span&gt; &lt;span class="nv"&gt;var.dest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in &lt;code&gt;vcl_error&lt;/code&gt;, construct the redirect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;obj.status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;601&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;obj.http.location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; obj.response&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;obj.status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;308&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; obj.response &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Permanent redirect"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;deliver&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Normalizing requests for better cache performance
&lt;/h2&gt;

&lt;p&gt;If you have a clear idea of what constitutes a valid URL for your site, it's helpful to use VCL to filter out anything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;fastly.ff.visits_this_service &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;req.restarts&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;# Sort query params into alphabetical order&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;req.url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; querystring.sort&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;req.url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;# Media assets have a specific set of allowed query params&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;req.url.path &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s2"&gt;"/media_[0-9a-f]{40,}[/a-zA-Z0-9_-]*\.[0-9a-z]+\z"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# width, height, format, optimize&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;req.url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; querystring.filter_except&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;req.url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"width"&lt;/span&gt; querystring.filtersep&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="s2"&gt;"height"&lt;/span&gt; querystring.filtersep&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="s2"&gt;"format"&lt;/span&gt; querystring.filtersep&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="s2"&gt;"optimize"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;# Paths ending .json have a different, specific set of allowed params&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;req.url.ext &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# limit, offset, sheet&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;req.url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; querystring.filter_except&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;req.url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"limit"&lt;/span&gt; querystring.filtersep&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="s2"&gt;"offset"&lt;/span&gt; querystring.filtersep&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="s2"&gt;"sheet"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;# Otherwise, query params are not allowed at all&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;req.url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; req.url.path&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not all websites have the luxury of knowing what parameters might be on the query strings of requests - and sometimes even if you do, you don't want to turn today's reality into tomorrow's limitation.  After all &lt;a href="https://www.fastly.com/blog/minimizing-ossification-risk-is-everyones-responsibility" rel="noopener noreferrer"&gt;ossification of the web&lt;/a&gt; is a major problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blocking unwanted traffic
&lt;/h2&gt;

&lt;p&gt;If you want a comprehensive solution to block malicious traffic, check out our &lt;a href="https://www.fastly.com/products/web-application-api-protection" rel="noopener noreferrer"&gt;Next-Gen WAF at Edge&lt;/a&gt;, but you can also construct simple rules in VCL using &lt;a href="https://www.fastly.com/documentation/guides/concepts/edge-state/dynamic-config/#access-control-lists" rel="noopener noreferrer"&gt;Access Control Lists&lt;/a&gt; or even just basic &lt;code&gt;if&lt;/code&gt; statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;fastly.ff.visits_this_service &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;req.restarts&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;req.http.user-agent&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s2"&gt;"/bot/"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="c1"&gt;// Header pattern&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;req.http.accept&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;              &lt;span class="c1"&gt;// Required header&lt;/span&gt;
    &lt;span class="nv"&gt;req.http.via&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;                  &lt;span class="c1"&gt;// Banned header&lt;/span&gt;
    client.geo.country_code &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s2"&gt;"^(fr|uk|ru)$"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="c1"&gt;// Country of origin&lt;/span&gt;
    &lt;span class="nv"&gt;client.ip&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; my_acl &lt;span class="o"&gt;||&lt;/span&gt;            &lt;span class="c1"&gt;// IP is in an ACL&lt;/span&gt;
    client.as.number &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;12345&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;     &lt;span class="c1"&gt;// Client's ISP&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    error &lt;span class="mi"&gt;625&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in &lt;code&gt;vcl_error&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;obj.status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;625&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;obj.status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; obj.response &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Forbidden"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;synthetic&lt;/span&gt; &lt;span class="s2"&gt;"Not allowed"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;deliver&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Strip upstream headers
&lt;/h2&gt;

&lt;p&gt;If you're using AWS S3, Google Cloud Storage or similar as a backend, they will likely return a whole bunch of response headers that you don't want.  Remove them with some VCL in &lt;code&gt;vcl_fetch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-generator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.via&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-github-request-id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-amz-delete-marker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-amz-id-2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-amz-request-id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-amz-version-id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-goog-component-count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-goog-expiration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-goog-generation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-goog-metageneration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-goog-stored-content-encoding&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;unset&lt;/span&gt; &lt;span class="nv"&gt;beresp.http.x-goog-stored-content-length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing this in &lt;code&gt;vcl_fetch&lt;/code&gt; makes sense because it happens before the object is written to cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging
&lt;/h2&gt;

&lt;p&gt;By default, Fastly includes certain response headers when a &lt;a href="https://www.fastly.com/documentation/reference/http/http-headers/Fastly-Debug/" rel="noopener noreferrer"&gt;&lt;code&gt;Fastly-Debug&lt;/code&gt;&lt;/a&gt; header is present in the request. It's handy to use this mechanism to add any other debugging information you want to emit.  For example, you could include the final request path that was used to look up the object in cache, and the version of your Fastly service that handled the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;req.http.fastly-debug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;resp.http.fastly-service-version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; req.vcl.version&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;resp.http.fastly-req-path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; req.url.path&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  You're off to a great start!
&lt;/h2&gt;

&lt;p&gt;These patterns are a great way to learn about the power of the edge to simplify and decouple layers of your service architecture, and apply to almost any use case. Implementing these also helps you understand some common &lt;a href="https://www.fastly.com/documentation/guides/vcl/best-practices/" rel="noopener noreferrer"&gt;best practices of VCL&lt;/a&gt; and set you up to implement solutions more specific to your use cases.&lt;/p&gt;

&lt;p&gt;Whatever you create, share it with us below or at &lt;a href="https://commuity.fastly.com" rel="noopener noreferrer"&gt;community.fastly.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>vcl</category>
      <category>edge</category>
    </item>
    <item>
      <title>Open source at Fastly is getting opener</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Fri, 15 Mar 2024 17:48:15 +0000</pubDate>
      <link>https://dev.to/fastly/open-source-at-fastly-is-getting-opener-804</link>
      <guid>https://dev.to/fastly/open-source-at-fastly-is-getting-opener-804</guid>
      <description>&lt;p&gt;&lt;strong&gt;Like most tech companies these days, Fastly is heavily reliant on open source software and the ecosystem of services and frameworks that modern software development weaves together. We need to talk about what it means to 'be open,' how we can recognize the broader ecosystem we're part of, and how we're making big changes to improve.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’re working to make our little corner of the open source world the friendliest, most welcoming, easiest to understand, and most inclusive place it can be. You’ll see the results of that work in three key new parts of our open platform we’re sharing today:  an &lt;a href="https://github.com/fastly/.github/blob/main/policy-docs/code-of-conduct.md" rel="noopener noreferrer"&gt;updated code of conduct&lt;/a&gt;, a new &lt;a href="https://www.fastly.com/documentation/developers/community/open-source/#featured-projects" rel="noopener noreferrer"&gt;project directory&lt;/a&gt;, and clearly-defined &lt;a href="https://www.fastly.com/documentation/developers/community/open-source/#understanding-topics-and-support-levels" rel="noopener noreferrer"&gt;support levels&lt;/a&gt; for our open work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv9dzdlymwzbwxh9cl3p1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv9dzdlymwzbwxh9cl3p1.png" alt="Screenshot of open source directory" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Making open understandable
&lt;/h2&gt;

&lt;p&gt;Recently, we identified a number of problems that made things unnecessarily complicated for users of our open source work.  We had a lot of public repositories on our GitHub but few people could easily understand which were active, whether support could be expected and whether contributions were invited. Many of our customers also use Fastly in conjunction with other tools, frameworks and services (like Gatsby, Heroku, NextJS, Vercel, Splunk, New Relic, Wordpress to name a few random ones), and adapters exist to support those interops, but it was often hard to figure out to what extent we support those adapters, whether they work for particular use cases, and who maintains them. This needed to be cleared up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making sure you have a good experience
&lt;/h2&gt;

&lt;p&gt;So, it’s not enough to provide a lot of great open source tools — you need to have a reliable, trusted experience when you use them. We started with the basics. GitHub has a very useful tool under Insights &amp;gt; Community standards on each repo, which provides a checklist for community health. This felt like a good starting point to help people understand the intent and support behind our public code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjpfnvt9c9ysqnj1zj1wi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjpfnvt9c9ysqnj1zj1wi.png" alt="Screenshot of GitHub community health dashboard" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, we took it a step further. We also created a clearly-documented support model to bundle up a bunch of expectations and behaviors into simple, recognisable buckets. Four levels can cover all projects quite well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product:&lt;/strong&gt; An official part of Fastly's services. Expect complete documentation and support engineers trained to help you out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 1 OSS:&lt;/strong&gt; "Active" projects that we are working on frequently and where you can expect enthusiastic engagement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 2 OSS:&lt;/strong&gt; "Maintenance mode" projects where we keep on top of dependencies and security issues but may not always be able to engage with community interest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Archived:&lt;/strong&gt; Projects that we don't intent to revisit, which may no longer work, and should be considered out of date and likely insecure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This can be nicely represented in a comparison matrix. You can see this on our new &lt;a href="https://developer.fastly.com/community/open-source/#understanding-topics-and-support-levels" rel="noopener noreferrer"&gt;open source directory&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxzer0xbh8lfkgr5417k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxzer0xbh8lfkgr5417k.png" alt="Screenshot showing table of support levels" width="800" height="651"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To tie all this together, we made an org profile for our &lt;a href="https://github.com/fastly" rel="noopener noreferrer"&gt;GitHub organization page&lt;/a&gt;.  There are some great examples of these, like &lt;a href="https://github.com/microsoft" rel="noopener noreferrer"&gt;Microsoft&lt;/a&gt; and &lt;a href="https://github.com/paypal" rel="noopener noreferrer"&gt;PayPal&lt;/a&gt;. For Fastly's, we wanted something friendly and human so we used &lt;a href="https://imagemagick.org/" rel="noopener noreferrer"&gt;Imagemagick&lt;/a&gt; to &lt;a href="https://github.com/benbalter/avatar-montage" rel="noopener noreferrer"&gt;create a montage&lt;/a&gt; of all the avatars of the Fastly team members who have contributed to the open source projects in our org:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllllpkrw8mg5dyew41tx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllllpkrw8mg5dyew41tx.png" alt="Screenshot of Fastly on GitHub" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is also a good spot to link to policies that apply across our open source work, and promote the great work being done by the organizations we support as part of our &lt;a href="https://www.fastly.com/fast-forward" rel="noopener noreferrer"&gt;Fast Forward program&lt;/a&gt;. More about that in a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wow I had no idea we had that!
&lt;/h2&gt;

&lt;p&gt;There's a ton of gold in our public repos, and scandalously, few people know it's there. Sorting through what we've open sourced over the years, we developed a category system, and most things fall into one of these buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools:&lt;/strong&gt; Stuff you run in your own environment, usually to help you interact with Fastly, like our &lt;a href="http://compute-js-static-publish" rel="noopener noreferrer"&gt;compute static publisher utility&lt;/a&gt; or the &lt;a href="https://github.com/fastly/cli" rel="noopener noreferrer"&gt;Fastly CLI&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugins:&lt;/strong&gt; Things you use inside third party products to help them integrate with Fastly, like our &lt;a href="https://github.com/fastly/WordPress-Plugin" rel="noopener noreferrer"&gt;Wordpress plugin&lt;/a&gt; or &lt;a href="https://github.com/fastly/terraform-provider-fastly" rel="noopener noreferrer"&gt;Terraform provider&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demos:&lt;/strong&gt; Applications that you can run on or with Fastly, demonstrating creative uses of the Fastly platform, like this &lt;a href="https://github.com/fastly/compute-js-auth" rel="noopener noreferrer"&gt;JavaScript implementation of OAuth 2.0&lt;/a&gt; you can run at the edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API clients:&lt;/strong&gt; Dedicated adapters to access api.fastly.com in a variety of languages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute Starter kits:&lt;/strong&gt; The &lt;a href="https://developer.fastly.com/learning/compute/" rel="noopener noreferrer"&gt;Fastly Compute&lt;/a&gt; platform allows you to run your code on our giant edge network in a variety of languages. Starter kits offer complete example Compute applications that are great for scaffolding new projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute SDKs:&lt;/strong&gt; These run within Fastly Compute apps and allow you to access platform features from your preferred language.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute libraries:&lt;/strong&gt; Code you can import into Compute apps to make common things easy or integrate with third party services or frameworks, like our &lt;a href="https://github.com/fastly/compute-js-opentelemetry" rel="noopener noreferrer"&gt;adapters for OpenTelemetry&lt;/a&gt; or &lt;a href="https://github.com/fastly/next-compute-js" rel="noopener noreferrer"&gt;Next.JS&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not all our open source repos are Fastly related either, Fastly engineering teams have opened up a bunch of things that we've built just to solve problems within low level parts of our stack.  For example &lt;a href="https://github.com/fastly/dnstap-utils" rel="noopener noreferrer"&gt;utilities for dnstap in Rust&lt;/a&gt;, or &lt;a href="https://github.com/fastly/sidekiq-prometheus" rel="noopener noreferrer"&gt;Prometheus instrumentation for Sidekiq&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do we really need a 9-year old fork of the Linux kernel?
&lt;/h2&gt;

&lt;p&gt;One of the things that stifles openness is &lt;em&gt;noise&lt;/em&gt;. Sifting through our public repos, some are very obviously redundant and just distracting.  So we started by archiving a whole bunch of them - over half, in fact. We wrote some &lt;a href="https://developers.google.com/apps-script" rel="noopener noreferrer"&gt;Google Apps Script&lt;/a&gt; in a spreadsheet to import and analyze the state of all our public repos:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfjv7x8p56qbx85t20yx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfjv7x8p56qbx85t20yx.png" alt="Screenshot of Google Sheets" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This allows for some basic filters to be applied to find the most obvious candidates for archiving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No updates in over a year&lt;/li&gt;
&lt;li&gt;No open issues&lt;/li&gt;
&lt;li&gt;Does not have forks&lt;/li&gt;
&lt;li&gt;Primary committer is no longer at Fastly&lt;/li&gt;
&lt;li&gt;Minimal watchers / stargazers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repos where all these things are true could be archived in bulk, and we reached out to the team closest to the code to give them the opportunity to delete the repo entirely if it was of no use at all. Archiving works well for everything else - &lt;a href="https://docs.github.com/en/repositories/archiving-a-github-repository/archiving-repositories" rel="noopener noreferrer"&gt;archived repos&lt;/a&gt; remain accessible and cloneable, so any unknown project that depends on them will continue to work, and it's reversible, so we can change our minds later if we need to. But archiving also sends a clear message that we are not paying attention to this code anymore, so use it at your own risk and don't expect any help!&lt;/p&gt;

&lt;p&gt;This captured a lot of fun examples, like &lt;a href="https://github.com/fastly/linux" rel="noopener noreferrer"&gt;a fork of the linux kernel&lt;/a&gt; that we last committed to 9 years ago and last merged from upstream 11 years ago 😅. But once these really obvious ones are out of the way, the real work begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code of conduct and responsible disclosure
&lt;/h2&gt;

&lt;p&gt;Once we finished cleaning up our repos, we wanted to add one more essential piece: A code of conduct. As a &lt;a href="https://www.fastly.com/code-of-business-conduct-and-ethics/" rel="noopener noreferrer"&gt;values-driven organization&lt;/a&gt;, we hold ourselves and those we interact with to a high standard of behavior. (That even extends to the kinds of customers we allow on our network because &lt;a href="https://www.fastly.com/code-of-business-conduct-and-ethics#:~:text=Just%20as%20we%20choose%20to,with%20those%20who%20do%20so." rel="noopener noreferrer"&gt;we choose to do business with those who reflect our values&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;But… which one to adopt, or should we write a new one? With so many excellent options, we quickly decided adopting an existing code of conduct would be the best path forward. &lt;/p&gt;

&lt;p&gt;The best way to build for our community is to build in collaboration with the community, so we simply &lt;a href="https://community.fastly.com/t/help-fastly-write-a-code-of-conduct-contributor-guidelines-for-our-github-org/2448?u=haubles" rel="noopener noreferrer"&gt;asked for input on our community forum&lt;/a&gt;. We received so many excellent responses and suggestions from community members and even other Fastly employees. People suggested the Contributor Covenant, the Django Code of Conduct, and Elastic’s CoC. &lt;/p&gt;

&lt;p&gt;After reviewing the suggestions against our needs, we chose to fork Python’s Code of Conduct, which is a fork of the example policy from &lt;a href="https://geekfeminism.fandom.com/wiki/Conference_anti-harassment/Policy" rel="noopener noreferrer"&gt;Geek Feminism wiki&lt;/a&gt;, created by the Ada Initiative and other volunteers, and is under a &lt;a href="https://creativecommons.org/publicdomain/zero/1.0/" rel="noopener noreferrer"&gt;Creative Commons Zero license&lt;/a&gt;. (This felt particularly fitting, considering Python is among our highest-traffic and longest-standing Fast Forward program members.) From there, we edited the Code to suit our circumstances and community needs. Our version is published on our GitHub profile.&lt;/p&gt;

&lt;p&gt;Finally, we needed a working group to help enforce the code of conduct, review possible violations, and determine a plan of action when infractions happen. So we formed Fastly’s OSS governance group, pulling from different backgrounds and disciplines across Fastly, including our inclusion and diversity, legal, engineering, and marketing teams. You can see the &lt;a href="https://github.com/fastly/.github/blob/main/policy-docs/reporting-and-enforcement.md#current-list-of-voting-members" rel="noopener noreferrer"&gt;members&lt;/a&gt; and &lt;a href="https://github.com/fastly/.github/blob/main/policy-docs/reporting-and-enforcement.md" rel="noopener noreferrer"&gt;enforcement policies&lt;/a&gt; on our GitHub. &lt;/p&gt;

&lt;h2&gt;
  
  
  Fast Forward
&lt;/h2&gt;

&lt;p&gt;Fastly is deeply committed to the open internet: we use open source software throughout our stack, contribute to open standards, and encourage employees to participate in and use open-source software.&lt;/p&gt;

&lt;p&gt;Given the benefits we derive from open source and standards, we must give back to open source builders and communities. That’s why we created &lt;a href="https://www.fastly.com/fast-forward" rel="noopener noreferrer"&gt;Fast Forward&lt;/a&gt; — Fastly’s mission to build the open internet together. This broad-reaching initiative comprises four key programs and policies: Our OSS contribution guidelines, our Customer Community Policy, our contributions to open standards, and the Fast Forward program.&lt;/p&gt;

&lt;p&gt;Through the Fast Forward program, we give free services and support to open source projects and the nonprofits that support them. We support many of the world’s top programming languages (like &lt;a href="https://www.python.org/" rel="noopener noreferrer"&gt;Python&lt;/a&gt;, &lt;a href="https://foundation.rust-lang.org/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt;, &lt;a href="https://www.ruby-lang.org/en/" rel="noopener noreferrer"&gt;Ruby&lt;/a&gt;, and the wonderful &lt;a href="https://www.scratchfoundation.org/" rel="noopener noreferrer"&gt;Scratch&lt;/a&gt;), foundational technologies (&lt;a href="https://curl.se/" rel="noopener noreferrer"&gt;cURL&lt;/a&gt;, the &lt;a href="https://www.kernel.org/" rel="noopener noreferrer"&gt;Linux kernel&lt;/a&gt;, &lt;a href="https://kubernetes.io/" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt;, &lt;a href="https://www.openstreetmap.org/" rel="noopener noreferrer"&gt;OpenStreetMap&lt;/a&gt;), and projects that make the internet better and more fun for everyone (&lt;a href="https://inkscape.org/" rel="noopener noreferrer"&gt;Inkscape&lt;/a&gt;, &lt;a href="https://joinmastodon.org/" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, &lt;a href="https://www.eff.org/" rel="noopener noreferrer"&gt;Electronic Frontier Foundation&lt;/a&gt;, &lt;a href="https://tosdr.org/" rel="noopener noreferrer"&gt;Terms of Service; Didn’t Read&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;We are always looking for new projects and ways to support the open internet. If you’re an open-source project that needs help scaling and protecting your website or applications, &lt;a href="https://www.fastly.com/fast-forward#apply-for-the-fast-forward-program" rel="noopener noreferrer"&gt;apply to the program&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Looking forward
&lt;/h2&gt;

&lt;p&gt;At Fastly, open source is a part of our heritage, essential to our operations, and central to our future. What’s more, we’re on a mission to build the good internet — whether you're building or browsing, an internet that is accessible and safe for everyone.&lt;/p&gt;

&lt;p&gt;Take a look at our &lt;a href="https://developer.fastly.com/community/open-source/" rel="noopener noreferrer"&gt;open source homepage&lt;/a&gt;, &lt;a href="https://www.fastly.com/fast-forward" rel="noopener noreferrer"&gt;Fast forward program&lt;/a&gt;, or join us on our &lt;a href="http://community.fastly.com" rel="noopener noreferrer"&gt;community forum&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>governance</category>
      <category>fastly</category>
      <category>community</category>
    </item>
    <item>
      <title>The crucial difference between robots.txt and x-robots-tag</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Mon, 27 Nov 2023 16:14:28 +0000</pubDate>
      <link>https://dev.to/fastly/the-crucial-difference-between-robotstxt-and-x-robots-tag-ofo</link>
      <guid>https://dev.to/fastly/the-crucial-difference-between-robotstxt-and-x-robots-tag-ofo</guid>
      <description>&lt;p&gt;“Robots” directives control how search engine crawlers are allowed to index your website, and are one of the oldest web standards. But does it matter if you use the &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag" rel="noopener noreferrer"&gt;meta tag&lt;/a&gt;, &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag" rel="noopener noreferrer"&gt;HTTP header&lt;/a&gt;, or the &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots/intro" rel="noopener noreferrer"&gt;robots.txt&lt;/a&gt; file? Yes, very much so.&lt;/p&gt;

&lt;p&gt;I hadn’t given this a huge amount of thought until I recently had a conversation with &lt;a href="https://csswizardry.com/" rel="noopener noreferrer"&gt;Harry Roberts&lt;/a&gt;, at the &lt;a href="https://perfnow.nl/" rel="noopener noreferrer"&gt;perf.now()&lt;/a&gt; conference. Harry made me realise that there is a significant difference between robots.txt and the other approaches to restricting indexing, which if not correctly understood might mean your site goes unindexed by Google and friends completely.&lt;/p&gt;

&lt;p&gt;Here’s an example robots.txt file telling a search crawler that it is not allowed to crawl anything under the &lt;code&gt;/api&lt;/code&gt; path on that domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;User-Agent: *
Disallow: /api
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the crawler will not even request anything (except the robots.txt file). But, where the robots.txt allows it, and the crawler does make a request for something on my site, I can still stop that response from being indexed by adding a meta tag to the markup, if the response is an HTML page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"robots"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"noindex"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course not everything is an HTML page, and Google and other search engines will often show documents like PDFs in search results, so a generally better approach is to use an HTTP header, which has the same effect and can be used on any type of response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;x-robots-tag: noindex
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This brings us to the trade offs between using robots.txt vs these per-response directives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sub-resources must be fetchable
&lt;/h2&gt;

&lt;p&gt;Search engines crawl using a real browser, and will therefore download all the images, CSS and scripts needed to render the page. If a script on the page makes API calls, the crawler will make those requests too.&lt;/p&gt;

&lt;p&gt;Imagine you have a site that requires an API response to fully render a page (eg as part of a React app that doesn’t do &lt;a href="https://web.dev/articles/rendering-on-the-web#server-side_rendering" rel="noopener noreferrer"&gt;server side rendering&lt;/a&gt;). The content inserted into the page from that API response will be indexable if the page itself is indexable, regardless of whether the API response had a &lt;code&gt;noindex&lt;/code&gt; directive on it. However, if the API request path is disallowed by robots.txt, the crawler won’t even make the request at all, and the page will be incomplete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pages need to be discoverable
&lt;/h2&gt;

&lt;p&gt;By using robots.txt to prevent resources from being fetched, you also prevent the crawler from discovering links to other resources. Conversely, a per-resource &lt;code&gt;noindex&lt;/code&gt; directive says “don’t index me”, but doesn’t stop the crawler from scanning the page to find things that are indexable (you can separately tell crawlers not to follow links in a page with the &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#directives" rel="noopener noreferrer"&gt;&lt;code&gt;nofollow&lt;/code&gt; directive&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In my experience crawlers have a creepy ability to find things even if you think they’re not linked to anywhere, but it’s certainly possible to make a piece of content accidentally undiscoverable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don’t invite unnecessary crawler traffic
&lt;/h2&gt;

&lt;p&gt;You might be getting the sense at this point that you should lean into using resource-level &lt;code&gt;noindex&lt;/code&gt; directives instead of robots.txt. But that comes with a different risk: of simply creating unnecessary requests to your servers. Crawlers tend to create traffic that’s more expensive to service, because they hit every page of your site. This forces your application stack to generate each page separately, even including obscure long-tail content for which crawlers might be the only regular clients.&lt;/p&gt;

&lt;p&gt;Certainly for situations where, for example, you want an entire domain to be off limits to a crawler, having it download every page only to find it has a &lt;code&gt;noindex&lt;/code&gt; directive is pretty wasteful.&lt;/p&gt;

&lt;p&gt;It would also be easy to forget to include the &lt;code&gt;noindex&lt;/code&gt; on some resources and end up having a site indexed by accident.  Robots.txt is certainly easier to apply comprehensively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the balance right
&lt;/h2&gt;

&lt;p&gt;So, taking the above scenarios into account, there are a few rules of thumb that make for a generally pretty good robots strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;where a whole domain should be off limits to crawlers, for example for staging/preview URLs, use robots.txt.&lt;/li&gt;
&lt;li&gt;Where resources should not be indexed but might be necessary to render indexable pages, they should be marked as &lt;code&gt;noindex&lt;/code&gt; using an &lt;code&gt;x-robots-tag&lt;/code&gt; header.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looking back at the earlier example of using robots.txt to disallow &lt;code&gt;/api&lt;/code&gt;: this seems likely to be a risky decision. Maybe responses from &lt;code&gt;/api&lt;/code&gt; would be better labelled with a &lt;code&gt;noindex&lt;/code&gt; directive instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use an edge network
&lt;/h2&gt;

&lt;p&gt;If you use an edge network like Fastly, it’s often possible to adjust the headers of responses before they are served to the end user. This can be handy for quickly adjusting the indexability of resources. Check out &lt;a href="https://developer.fastly.com/solutions/examples/add-remove-or-change-http-headers/" rel="noopener noreferrer"&gt;this example&lt;/a&gt; to learn how to add and remove headers. You could even consider generating and serving the robots.txt file from the edge too. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Visualize your Fastly traffic on a real time globe using Glitch</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Wed, 09 Aug 2023 13:37:34 +0000</pubDate>
      <link>https://dev.to/fastly/visualize-your-fastly-traffic-on-a-real-time-globe-using-glitch-9di</link>
      <guid>https://dev.to/fastly/visualize-your-fastly-traffic-on-a-real-time-globe-using-glitch-9di</guid>
      <description>&lt;p&gt;On the &lt;a href="https://developer.fastly.com" rel="noopener noreferrer"&gt;Fastly developer hub&lt;/a&gt;, we recently added a visualization of the traffic flowing across the Fastly edge network.  It was a big hit and customers ask me how they can visualize their own traffic like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qcdu3r68p9aw8tcr0wt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qcdu3r68p9aw8tcr0wt.png" alt="Screenshot of developer.fastly.com showing a globe visualization" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fear not.  I've repackaged the React component we made to do this as a Glitch app, so you can create your very own globe.  Not only that, but a lot of the feedback I was getting was stuff like "This would look great on the giant screen in our lobby" so we made a non-interactive version designed to fill a 16:9 screen.  Let's dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make a log processor app
&lt;/h2&gt;

&lt;p&gt;First, you need somewhere for Fastly to send your log data.  A Glitch app is ideal for this (Glitch is a collaborative and free code playground which is now part of Fastly).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remix the &lt;a href="https://glitch.com/edit/#!/fastly-globeviz-data" rel="noopener noreferrer"&gt;fastly-globeviz-data&lt;/a&gt; Glitch app&lt;/li&gt;
&lt;li&gt;Note the URL that Glitch assigns to your new app, eg &lt;code&gt;monthly-ferret-taxi.glitch.me&lt;/code&gt; or &lt;code&gt;glitch.com/edit/#!/monthly-ferret-taxi&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What does this app do?  It's a one-file ExpressJS app that has a few route handlers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;POST requests to &lt;code&gt;/&lt;/code&gt; are unpacked and each line is treated as an event to emit on a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;Server sent events&lt;/a&gt; stream.&lt;/li&gt;
&lt;li&gt;GET requests to &lt;code&gt;/&lt;/code&gt; receive a streaming response that includes any events as they are created.&lt;/li&gt;
&lt;li&gt;A GET request to &lt;code&gt;/.well-known/fastly/logging/challenge&lt;/code&gt; is answered as required by Fastly's &lt;a href="https://developer.fastly.com/learning/integrations/logging/#http-challenge" rel="noopener noreferrer"&gt;log challenge rules&lt;/a&gt; for HTTP endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you have your own log collector, it's time to send some data to it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrument your Fastly service
&lt;/h2&gt;

&lt;p&gt;Configure your Fastly service to emit traffic data to the Glitch app you just created.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a Fastly service if you don't have one already.  If you do, create a new draft version of the service.&lt;/li&gt;
&lt;li&gt;Decide on a three character code to identify this traffic, e.g. if you are the New York Times, you could use &lt;code&gt;NYT&lt;/code&gt;.  This is so that if you want to you can send multiple different website traffic streams to the same globe visualization, you can identify them and colour code them.&lt;/li&gt;
&lt;li&gt;Decide on a sample rate.  A good rate to sample is 20-50 requests per second.  If that represents roughly a thousandth of your traffic, the sample rate would be 1000.  A sample rate of 1 is 100% of traffic.  Err on the side of sampling too little and dial it up, otherwise you may trigger rate limiting on Glitch.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following VCL as an &lt;strong&gt;INIT&lt;/strong&gt; VCL snippet:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;table globeviz_config {
  "service_name": "ABC", // Change this value
  "sample_rate": "1000"  // Change this value
}

// https://deviceatlas.com/device-data/explorer/#defined_property_values/7/2705619
table globeviz_known_browsers {
  "Chrome": "C",
  "Chrome Mobile": "C",
  "Firefox": "F",
  "Firefox for Mobile": "F",
  "Edge": "E",
  "Kindle Browser": "K",
  "Safari": "S",
  "Samsung Browser": "A"
}

// Record the following data (space separated)
//    PIO             Name of source service (3 char)
//    LCY             Name of Fastly POP (3 char)
//    51.43,-0.23     Client location (var length)
//    17234           Duration (var length)
//    EN              Normalized Accept-Language (2 char)
//    M               Cache state (1 char: H, M, P)
//    2               HTTP version (numeric, variable length)
//    1.2             TLS version (numeric, variable length)
//    C               Browser (1 char: C, F, E, K, S, A)
//
sub vcl_log {
  // Record only non-shielding, non-VPN requests at the specified sample frequency
  if (fastly.ff.visits_this_service == 0 &amp;amp;&amp;amp; client.geo.proxy_type == "?" &amp;amp;&amp;amp; client.geo.latitude != 0 &amp;amp;&amp;amp; randombool(1,std.atoi(table.lookup(globeviz_config, "sample_rate", "100000")))) {
    log "syslog " req.service_id " fastly-globeviz :: "
      table.lookup(globeviz_config, "service_name", "-") " "
      server.datacenter " "
      client.geo.latitude "," client.geo.longitude " "
      time.elapsed.usec " "
      std.toupper(
        accept.language_lookup(
          "en:de:fr:nl:jp:es:ar:zh:gu:he:hi:id:it:ko:ms:pl:pt:ru:th:uk",
          "en",
          req.http.Accept-Language
        )
      ) " "
      substr(fastly_info.state, 0, 1) " "
      regsuball(req.proto, "[^\d.]", "") " "
      regsuball(tls.client.protocol, "[^\d.]", "") " "
      table.lookup(globeviz_known_browsers, client.browser.name, "Z")
    ;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create an HTTPS &lt;a href="https://developer.fastly.com/learning/integrations/logging/" rel="noopener noreferrer"&gt;log endpoint&lt;/a&gt; called &lt;code&gt;fastly-globeviz&lt;/code&gt; pointing to your Glitch URL (choose a content type of "text/plain" and a placement of "none").  You can do that in the UI or with this cURL command:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -i  -X POST "https://api.fastly.com/service/$SERVICE_ID/version/$VERSION/logging/https" -H "Fastly-Key: $YOUR_FASTLY_TOKEN" -H "Content-Type: application/x-www-form-urlencoded" -H "Accept: application/json" -d "name=fastly-globeviz&amp;amp;placement=none&amp;amp;content_type=text/plain&amp;amp;url=https://$GLITCH_APP_ID.glitch.me/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Activate the new version of your service&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Test the log receiver
&lt;/h2&gt;

&lt;p&gt;Open the URL of your glitch app in a browser and you should start to see traffic log data streaming in.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo019ab4x8wig0mib619a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo019ab4x8wig0mib619a.png" alt="Browser screenshot" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the data your Fastly service is emitting.  This is a good time to check that you're happy with the type of data you're sharing with the world, since the Glitch app you're sending it to is exposing it publicly.&lt;/p&gt;

&lt;h2&gt;
  
  
  View it on a map!
&lt;/h2&gt;

&lt;p&gt;Now you can visualize your log data on a globe.  You don't actually need your own version of the globe, so you don't need to remix an app for this, you can just use mine directly - and point it at your data source.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://fastly-globeviz-ui.glitch.me/" rel="noopener noreferrer"&gt;https://fastly-globeviz-ui.glitch.me/&lt;/a&gt; in your browser.  You should see traffic animating, but that's not your traffic right now, it's the default traffic source.&lt;/li&gt;
&lt;li&gt;Add a query parameter &lt;code&gt;?SOURCE_URL=&amp;lt;your log collector&amp;gt;&lt;/code&gt; to the URL, e.g. &lt;code&gt;?SOURCE_URL=https://monthly-ferret-taxi.glitch.me&lt;/code&gt;.  You should now see your data!&lt;/li&gt;
&lt;li&gt;Go look at &lt;a href="https://glitch.com/edit/#!/fastly-globeviz-ui?path=src%2Fconfig.js%3A1%3A0" rel="noopener noreferrer"&gt;the &lt;code&gt;src/config.js&lt;/code&gt; file&lt;/a&gt; in the UI Glitch app.  This is where you can see all the variables that are configurable.  &lt;code&gt;SOURCE_URL&lt;/code&gt; was one of them, but they are all overridable by adding more query parameters to your URL.  If you prefer, you can remix the app and edit &lt;code&gt;config.js&lt;/code&gt; directly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Give feedback and contribute
&lt;/h2&gt;

&lt;p&gt;Both the Glitch apps are remixable, so go ahead and make changes, and let us know what you come up with in the &lt;a href="https://community.fastly.com" rel="noopener noreferrer"&gt;Fastly community forum&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>data</category>
      <category>visualization</category>
      <category>webgl</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Who kept the bots out? Stopping content being harvested by AI</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Thu, 22 Jun 2023 09:57:18 +0000</pubDate>
      <link>https://dev.to/fastly/who-kept-the-bots-out-stopping-content-being-harvested-by-ai-4599</link>
      <guid>https://dev.to/fastly/who-kept-the-bots-out-stopping-content-being-harvested-by-ai-4599</guid>
      <description>&lt;p&gt;AI-powered content generation has exploded in popularity recently, with bots like &lt;a href="https://chat.openai.com" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt; and &lt;a href="https://bard.google.com/" rel="noopener noreferrer"&gt;Bard&lt;/a&gt;, but the giant amounts of data these bots require comes from harvesting the web. What if you don’t want your content feeding the bots? Some respect &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots/intro" rel="noopener noreferrer"&gt;robots.txt&lt;/a&gt;, others notice a new ‘noai’ header tag.&lt;/p&gt;

&lt;p&gt;An &lt;a href="https://www.vice.com/en/article/dy3vmx/an-ai-scraping-tool-is-overwhelming-websites-with-traffic" rel="noopener noreferrer"&gt;article in Vice&lt;/a&gt; recently drew attention to the way AI bots are harvesting the web, in some cases quite aggressively. Site owners quite reasonably want to protect the originality of their creative works, assert their copyrights, and also not have to deal with traffic surges from ill-configured or badly-behaving scraper bots.&lt;/p&gt;

&lt;p&gt;The particular tool mentioned in the Vice article is &lt;a href="https://github.com/rom1504/img2dataset" rel="noopener noreferrer"&gt;Img2dataset&lt;/a&gt;, and right now, it doesn't pay attention to the robots.txt file, the normal mechanism you can use to dissuade well behaved bots from indexing your content.  However, it does &lt;a href="https://github.com/rom1504/img2dataset/issues/293" rel="noopener noreferrer"&gt;respect a new HTTP header directive&lt;/a&gt;, &lt;code&gt;X-Robots-Tag: noai&lt;/code&gt; (and also &lt;code&gt;noindex&lt;/code&gt;, though that's an existing and already well-known part of the robots.txt standard).&lt;/p&gt;

&lt;p&gt;A lot of Fastly customers have multiple websites running through Fastly, and in many cases multiple backend servers feeding a single public domain.  Managing the addition of HTTP metadata at the origin application can be tedious and error prone, but fortunately applying it at the edge is pretty simple.&lt;/p&gt;

&lt;p&gt;If you have a &lt;a href="https://developer.fastly.com/learning/vcl/" rel="noopener noreferrer"&gt;VCL powered Fastly service&lt;/a&gt;, you can add a header in a deliver snippet or in the vcl_deliver subroutine of your custom VCL code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vcl"&gt;&lt;code&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;resp.http.x-robots-tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"noai"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's &lt;a href="https://fiddle.fastly.dev/fiddle/68cdc262" rel="noopener noreferrer"&gt;a fiddle you can play with&lt;/a&gt; demonstrating that code.&lt;/p&gt;

&lt;p&gt;If you are using our new &lt;a href="https://developer.fastly.com/learning/compute/" rel="noopener noreferrer"&gt;Compute@Edge platform&lt;/a&gt;, setting a header varies from one language to another.  There's a &lt;a href="https://developer.fastly.com/solutions/examples/add-remove-or-change-http-headers" rel="noopener noreferrer"&gt;generic example in our developer hub&lt;/a&gt; covering all languages we support, but as an example, here's how to do it in JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-robots-tag&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And again, &lt;a href="https://fiddle.fastly.dev/fiddle/fa0d0b36" rel="noopener noreferrer"&gt;here's a fiddle with that code&lt;/a&gt; you can remix and try for yourself.&lt;/p&gt;

&lt;p&gt;Of course this just fixes the issue for one bot, and while others might pick up on signals from robots.txt, some are just going to be stubbornly insistent on crawling and downloading your content regardless.  &lt;strong&gt;These bots should be considered bad actors and blocked.&lt;/strong&gt;  Fastly has lots of ways to do this - manual logic can be written in both VCL and all Compute languages to block based on signals such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ASN (Autonomous system number): the block of IPs owned by a single network operator&lt;/li&gt;
&lt;li&gt;Explicit IP addresses or ranges&lt;/li&gt;
&lt;li&gt;HTTP headers such as &lt;code&gt;User-Agent&lt;/code&gt; or &lt;code&gt;Accept&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Originating traffic location&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's &lt;a href="https://developer.fastly.com/solutions/examples/dictionary-based-ip-block-list" rel="noopener noreferrer"&gt;an example of banning by IP address&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You might like to use challenges to separate out bot traffic, such as a &lt;a href="https://developer.fastly.com/solutions/examples/captcha-challenge" rel="noopener noreferrer"&gt;CAPTCHA&lt;/a&gt; or proof of work, or use &lt;a href="https://developer.fastly.com/solutions/examples/verify-if-a-web-crawler-accessing-your-server-really-is-googlebot" rel="noopener noreferrer"&gt;DNS lookups to authenticate good crawlers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, if you want more of a magic bullet, consider enabling our &lt;a href="https://docs.fastly.com/products/fastly-next-gen-waf" rel="noopener noreferrer"&gt;Next-gen Web Application Firewall&lt;/a&gt; on your service, which will detect anomalous behaviour, alert and block attacks automatically.&lt;/p&gt;

&lt;p&gt;If you want to get inspiration for what else you could be doing with edge compute technologies, check out the &lt;a href="https://developer.fastly.com/solutions/" rel="noopener noreferrer"&gt;solutions area on the Fastly developer hub&lt;/a&gt;.  Happy coding!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>scraping</category>
      <category>http</category>
    </item>
    <item>
      <title>Updating Monaco broke Fastly Fiddle: here's how I solved it with useCallback in React</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Wed, 19 Apr 2023 15:07:23 +0000</pubDate>
      <link>https://dev.to/fastly/updating-monaco-broke-fastly-fiddle-heres-how-i-solved-it-with-usecallback-in-react-2j92</link>
      <guid>https://dev.to/fastly/updating-monaco-broke-fastly-fiddle-heres-how-i-solved-it-with-usecallback-in-react-2j92</guid>
      <description>&lt;p&gt;My colleague &lt;a href="https://doramilitaru.com/" rel="noopener noreferrer"&gt;Dora&lt;/a&gt; and I recently updated &lt;a href="https://fiddle.fastly.dev/" rel="noopener noreferrer"&gt;Fastly Fiddle&lt;/a&gt;'s dependencies, and we suddenly found that user input in our code editor was erratic and unusably slow.  We fixed it by moving some state from the component into a local variable, persisted across renders thanks to &lt;a href="https://react.dev/reference/react/useCallback" rel="noopener noreferrer"&gt;useCallback&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is what we saw in the UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanf4rjc9le5aoejwqe36.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanf4rjc9le5aoejwqe36.gif" alt="Keystrokes disappear on entry" width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Popping open the network panel, we can see a request to the server for every keystroke, 1 second after the key was pressed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyiibwznmavl2oryaiwmk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyiibwznmavl2oryaiwmk.gif" alt="Lots of network requests" width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ouch. Our debounce is clearly broken - and not only that but we're also supposed to be cancelling save requests that are already in progress if a new keystroke is made, and that clearly isn't working either.&lt;/p&gt;

&lt;p&gt;It turns out that the bottom line is, &lt;strong&gt;if you read state in a function that's been &lt;a href="https://codeburst.io/understanding-memoization-in-3-minutes-2e58daf33a19" rel="noopener noreferrer"&gt;memoized&lt;/a&gt;, you will get an out of date version of that state.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In our case, &lt;a href="https://microsoft.github.io/monaco-editor/" rel="noopener noreferrer"&gt;Monaco&lt;/a&gt;, the code editor component, was memoizing a function, invoking an outdated version of it, and as a result we were never able to tell that a save operation was already in progress, so we didn't cancel the existing operation, and cued up a new one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problematic code
&lt;/h2&gt;

&lt;p&gt;Many performance problems and behavioural bugs in React applications seem to stem from a too-hazy understanding of what causes a component to re-render, what happens when a component re-renders, and in what context a function is being invoked. Fortunately I am friends with &lt;a href="https://twitter.com/iamakulov" rel="noopener noreferrer"&gt;Ivan Akulov&lt;/a&gt; who is basically web performance in human form, and he helped me figure this out.&lt;/p&gt;

&lt;p&gt;In Fastly Fiddle, we have a state variable &lt;code&gt;saveOperation&lt;/code&gt; which is read and updated by a &lt;code&gt;saveHandler&lt;/code&gt; function, and which is also passed down into child components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;saveOperation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSaveOperation&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saveHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saveOperation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;saveOperation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;debounceDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abortableWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEBOUNCE_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;setSaveOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;debounceDelay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;debounceDelay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nf"&gt;setSaveOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Main&lt;/span&gt; &lt;span class="nx"&gt;onSave&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;saveHandler&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're trying to wait for an idle period before saving the user's work (a 'debounce').  Every time we see new keystrokes, we abort the pending save - or any in-flight API request that is already in progress.&lt;/p&gt;

&lt;p&gt;We're doing this using an &lt;a href="https://zzdjk6.medium.com/a-simple-implementation-of-abortable-promise-using-abortcontroller-with-typescript-2c163ee050e8" rel="noopener noreferrer"&gt;abortable promise pattern&lt;/a&gt; - there's probably a more idiomatic way to do this in React but this seemed to work pretty well.  &lt;code&gt;saveHandler&lt;/code&gt; is going to get redefined every time the component renders, but that should be fine - &lt;code&gt;saveOperation&lt;/code&gt; is component state, which will persist across renders.  Right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Memoization problem
&lt;/h2&gt;

&lt;p&gt;However, there's a risk - if anything further down the component tree memoizes the reference to &lt;code&gt;saveHandler&lt;/code&gt;, we might end up calling an out of date version of the function which has captured an out of date version of &lt;code&gt;saveOperation&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Turns out, this is exactly what's happening.  After our dependency update, we discovered that when keystrokes were entered into a code editor component powered by &lt;a href="https://microsoft.github.io/monaco-editor/" rel="noopener noreferrer"&gt;Monaco&lt;/a&gt;, the resulting invocation of &lt;code&gt;saveHandler&lt;/code&gt; found &lt;code&gt;saveOperation&lt;/code&gt; to be &lt;code&gt;undefined&lt;/code&gt; every time. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffgs94k345cgkhx26gol8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffgs94k345cgkhx26gol8.png" alt="saveOperation is always undefined" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;Main&amp;gt;&lt;/code&gt; component eventually passes the &lt;code&gt;saveHandler&lt;/code&gt; function to Monaco like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MonacoEditor&lt;/span&gt;
  &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fiddle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;codeContext&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;monacoOptions&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saveHandler&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new version of Monaco then memoizes the &lt;code&gt;onChange&lt;/code&gt; callback, which means that when keystrokes are entered into the editor, the &lt;code&gt;saveHandler&lt;/code&gt; that gets fired is one that doesn't have the latest value of &lt;code&gt;saveOperation&lt;/code&gt;, so we don't know there's a save already in progress, so we don't cancel it, and all hell breaks loose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Progress state is an antipattern?
&lt;/h2&gt;

&lt;p&gt;So what's the solution?  I could investigate why Monaco is memoizing the handler, and maybe try and convince it to not do that, but this problem highlights that using &lt;code&gt;useState&lt;/code&gt; to store &lt;code&gt;saveOperation&lt;/code&gt; is maybe not the best idea in the first place.  Apart from making the function dependent on data in the parent context, using state also means we &lt;em&gt;re-render the component every time we update it&lt;/em&gt;.  Since &lt;code&gt;saveOperation&lt;/code&gt; doesn't affect the component's render output, that doesn't seem right.&lt;/p&gt;

&lt;p&gt;I've decided to call this kind of data "progress state", since it effectively carries data forward from one invocation of a function to the next, to allow a subsequent invocation to adjust its behaviour based on knowing where we left off in the previous one.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useState&lt;/code&gt; is the most obvious way to do this, but it's not a good one.  The React-y way to do this is actually &lt;code&gt;useRef&lt;/code&gt;:&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 1: useRef
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saveOperation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saveHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saveOperation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;saveOperation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;debounceDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abortableWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEBOUNCE_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;saveOperation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;debounceDelay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;debounceDelay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;saveOperation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Updating &lt;code&gt;saveOperation&lt;/code&gt; now won't trigger a render, and this will work even if &lt;code&gt;saveHandler&lt;/code&gt; gets memoized.&lt;/p&gt;

&lt;p&gt;The reason this works is the same reason &lt;code&gt;useRef&lt;/code&gt; variables always have that weird &lt;code&gt;.current&lt;/code&gt; thingy hanging off them.  The variable created by &lt;code&gt;useRef&lt;/code&gt; is immutable, so capturing it into a function is fine, because we know it cannot be reassigned: the value of &lt;code&gt;saveOperation&lt;/code&gt; (which is an object) &lt;em&gt;will never change&lt;/em&gt;.  However, the &lt;em&gt;properties&lt;/em&gt; of that object &lt;em&gt;can&lt;/em&gt; change, and a function that has captured a reference to the object will see those updated properties.  That's why refs have a &lt;code&gt;.current&lt;/code&gt; property.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 2: useCallback closures
&lt;/h2&gt;

&lt;p&gt;React's &lt;code&gt;useCallback&lt;/code&gt; hook creates a reference to a function and then reuses it in subsequent renders of the component, rather than redefining the function every time.  We figured, you could use this with an Immediately Invoked Function Expression (IIFE) to capture a value that persists between renders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saveHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;saveOperation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saveOperation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;saveOperation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;saveOperation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abortableWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEBOUNCE_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;saveOperation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="nx"&gt;saveOperation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;})(),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when the component is first rendered, React will execute the IIFE, and assign the resulting function to &lt;code&gt;saveHandler&lt;/code&gt;.  The returned function has access to &lt;code&gt;saveOperation&lt;/code&gt; which is trapped in a closure created by the IIFE.&lt;/p&gt;

&lt;p&gt;I like this solution because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;it leans more on fundamentals of JavaScript rather than relying on the framework to solve the problem&lt;/li&gt;
&lt;li&gt;it creates a tighter scope for my data (&lt;code&gt;saveOperation&lt;/code&gt; is not accessible outside of &lt;code&gt;saveHandler&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;It's the simplest use of &lt;code&gt;saveOperation&lt;/code&gt; - no need to use a setter function, and no need to use a &lt;code&gt;.current&lt;/code&gt; property.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, &lt;a href="https://react.dev/reference/react/useCallback#caveats" rel="noopener noreferrer"&gt;React's docs on &lt;code&gt;useCallback&lt;/code&gt;&lt;/a&gt; have this caveat:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the future, React may add more features that take advantage of throwing away the cache — for example, if React adds built-in support for virtualized lists in the future, it would make sense to throw away the cache for items that scroll out of the virtualized table viewport. This should match your expectations if you rely on useCallback as a performance optimization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So in future, my callback might get redefined more often... but for a use case like debouncing, it seems like that would still be fine most of the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There are some nice lessons in this debugging exercise: Don't overuse &lt;code&gt;useState&lt;/code&gt;. Learn to love &lt;code&gt;useRef&lt;/code&gt;. And it's nice that React has a solution to this &lt;em&gt;and&lt;/em&gt; that we can also solve it using JS language primitives.&lt;/p&gt;

&lt;p&gt;Fine, go ahead and flame me for doing React wrong. I don't care, React is weird anyway.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Filter PNGs for Acropalypse using Compute@Edge</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Thu, 23 Mar 2023 10:11:26 +0000</pubDate>
      <link>https://dev.to/fastly/filter-pngs-for-acropalypse-using-computeedge-1c58</link>
      <guid>https://dev.to/fastly/filter-pngs-for-acropalypse-using-computeedge-1c58</guid>
      <description>&lt;p&gt;Last week, Simon Aaarons and David Buchanan posted &lt;a href="https://twitter.com/ItsSimonTime/status/1636857478263750656?s=20" rel="noopener noreferrer"&gt;their discovery&lt;/a&gt; that images cropped using Android's Markup editor app often hid within them the original uncropped image.  David went on to suggest that &lt;a href="https://twitter.com/david3141593/status/1638293029059477505" rel="noopener noreferrer"&gt;CDNs could transparently mitigate the vulnerability&lt;/a&gt; by deploying a filter at the edge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge accepted!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The easy answer
&lt;/h2&gt;

&lt;p&gt;We already have an &lt;a href="https://developer.fastly.com/reference/io/" rel="noopener noreferrer"&gt;Image optimization&lt;/a&gt; feature, and we've verified that &lt;strong&gt;any image that passes through our optimizer will be stripped of trailing content&lt;/strong&gt;.  That means if you use IO on your Fastly service, you're already filtering images such that the acropalypse data will be removed.&lt;/p&gt;

&lt;p&gt;However, not everyone uses image optimization with their Fastly service. So what if you don't?&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the problem
&lt;/h2&gt;

&lt;p&gt;PNG files have a well known byte sequence that identifies them, the "magic bytes", which are these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;89 50 4E 47
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see this in a &lt;code&gt;hexdump&lt;/code&gt; of any PNG file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;○ head -c 100 Screenshot\ 2023-03-01\ at\ 10.35.01.png | hexdump -C
00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
00000010  00 00 06 cc 00 00 04 e6  08 06 00 00 00 85 ef 84  |................|
00000020  f2 00 00 0a aa 69 43 43  50 49 43 43 20 50 72 6f  |.....iCCPICC Pro|
00000030  66 69 6c 65 00 00 48 89  95 97 07 50 53 e9 16 c7  |file..H....PS...|
00000040  bf 7b d3 43 42 49 42 28  52 42 6f 82 74 02 48 09  |.{.CBIB(RBo.t.H.|
00000050  a1 85 de 9b a8 84 24 40  28 21 26 04 05 bb b2 b8  |......$@(!&amp;amp;.....|
00000060  82 6b 41 44                                       |.kAD|
00000064
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PNG files end with a similar marker, called IEND, which is also easy to spot in the byte stream - a sequence of four null bytes and the ASCII characters IEND:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;○ &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 100 Screenshot&lt;span class="se"&gt;\ &lt;/span&gt;2023-03-01&lt;span class="se"&gt;\ &lt;/span&gt;at&lt;span class="se"&gt;\ &lt;/span&gt;10.35.01.png | hexdump &lt;span class="nt"&gt;-C&lt;/span&gt;
00000000  98 cf ff 9d 2f 79 c0 7b  c3 ec de 7b ef 8d 1b 96  |..../y.&lt;span class="o"&gt;{&lt;/span&gt;...&lt;span class="o"&gt;{&lt;/span&gt;....|
00000010  2c 0b 1e f0 24 c3 a2 32  f9 4a fd 95 c6 3a 7c 13  |,...&lt;span class="nv"&gt;$.&lt;/span&gt;.2.J...:|.|
00000020  a1 1a 78 6c 47 70 c6 fb  dc 41 &lt;span class="nb"&gt;dd &lt;/span&gt;f2 8d 4b 79 d1  |..xlGp...A...Ky.|
00000030  35 f2 6b 89 ef f0 d8 3e  e8 ed 23 70 c6 3f 5e 6f  |5.k....&amp;gt;..#p.?^o|
00000040  6f 98 75 80 4b 7c da 01  1e 1c 72 80 93 a8 ff 37  |o.u.K|....r....7|
00000050  c8 1a 61 82 9a 74 43 f6  00 00 00 00 49 45 4e 44  |..a..tC.....IEND|
00000060  ae 42 60 82                                       |.B&lt;span class="sb"&gt;`&lt;/span&gt;.|
00000064
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is that some tools, notably the markup tool on Android, when cropping an image, will &lt;em&gt;include the original image data in the leftover space at the the end of the file&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/ItsSimonTime/status/1636857478263750656?s=20" rel="noopener noreferrer"&gt;Simon's post&lt;/a&gt; helpfully illustrates the issue:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh94evattiz7civ5zcb9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh94evattiz7civ5zcb9a.png" alt="Illustration of the Acropalypse vulnerability" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OK, so how can we stop this from leaking our user's private data?&lt;/p&gt;

&lt;h2&gt;
  
  
  Compute@Edge to the rescue
&lt;/h2&gt;

&lt;p&gt;Let's crack open &lt;a href="https://fiddle.fastly.dev/" rel="noopener noreferrer"&gt;Fastly Fiddle&lt;/a&gt;, and write some code at the edge.  To start, I'll make a basic request handler, which sends the request to a backend, and passes the response back to the client.&lt;/p&gt;

&lt;p&gt;That looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;origin_0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;acropalypseFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;acropalypseFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in Fiddle you'll see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zecw4vgrlvglmxzpg9n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zecw4vgrlvglmxzpg9n.png" alt="Fiddle UI" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can configure my fiddle to use &lt;code&gt;http-me.glitch.me&lt;/code&gt; as a backend, a server Fastly maintains to provide responses that can be handy for testing this kind of thing.  The path can be set to &lt;code&gt;/image-png&lt;/code&gt; to get HTTP-me to give me a PNG image.&lt;/p&gt;

&lt;p&gt;Now I need to flesh out my &lt;code&gt;acropalypseFilter&lt;/code&gt; function.  Here is what I came up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;acropalypseFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Define the byte sequences for the PNG magic bytes and the IEND marker&lt;/span&gt;
  &lt;span class="c1"&gt;// that identifies the end of the image&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pngMarker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0x89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x4e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x47&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pngIEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x49&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x4e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x44&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Define an async function so we can use await when processing the stream&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processChunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isPNG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isIEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="c1"&gt;// Fetch a chunk from the input stream&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// If we have not yet found a PNG marker, see if there's one&lt;/span&gt;
      &lt;span class="c1"&gt;// in this chunk.  If there is, we have a PNG that is potentially&lt;/span&gt;
      &lt;span class="c1"&gt;// vulnerable&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isPNG&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;seqFind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pngMarker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;It's a PNG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;isPNG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// If we know we're past the end of the PNG, any remaining data&lt;/span&gt;
      &lt;span class="c1"&gt;// in the file is the hidden data we want to remove.  Since we already&lt;/span&gt;
      &lt;span class="c1"&gt;// sent the Content-Length header, we'll pad the rest of the response&lt;/span&gt;
      &lt;span class="c1"&gt;// with zeroes.&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isIEND&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// If it's a PNG but we're yet to get to the end of it...&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPNG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// See if this chunk contains the IEND marker&lt;/span&gt;
        &lt;span class="c1"&gt;// If so, output the data up to the marker and replace the rest of the&lt;/span&gt;
        &lt;span class="c1"&gt;// chunk with zeroes.&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;seqFind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pngIEND&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Found IEND at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;isIEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
          &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
          &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Either we're not dealing with a PNG, or we're in a PNG but have not&lt;/span&gt;
      &lt;span class="c1"&gt;// reached the IEND marker yet. Either way, we can simply copy the&lt;/span&gt;
      &lt;span class="c1"&gt;// chunk directly to the output.&lt;/span&gt;
      &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// After the input stream ends, we should do cleanup.&lt;/span&gt;
    &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;readable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TransformStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWriter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;processChunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's step through this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We set up some variables to hold the byte sequences that will tell us whether the response is a PNG and also whether we've reached the end of it.&lt;/li&gt;
&lt;li&gt;Skipping to the bottom, the &lt;code&gt;if (response.body)&lt;/code&gt; block creates a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/TransformStream" rel="noopener noreferrer"&gt;TransformStream&lt;/a&gt;, and returns a new &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response" rel="noopener noreferrer"&gt;Response&lt;/a&gt; that consumes the readable side of it.  Now we need to read from the backend response, and push data into the writable side of the TransformStream.&lt;/li&gt;
&lt;li&gt;Since we want to return a &lt;code&gt;Response&lt;/code&gt; from the &lt;code&gt;acropalypseFilter&lt;/code&gt; function, the &lt;code&gt;acropalypseFilter&lt;/code&gt; function cannot be async (if it was declared &lt;code&gt;async&lt;/code&gt; it would return a Promise, not a Response).  So instead, I've defined a child function, &lt;code&gt;processChunks&lt;/code&gt;, which can be async.  We can also call it without awaiting the Promise, because it's OK to return the Response before the stream has ended (better, actually!)&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;processChunks&lt;/code&gt;, we create a read loop and read chunks of data from the backend response.  We are looking for the magic marker that says the file is a PNG.  If we find it, we're then looking for the marker that indicates the end of the PNG.  If we find &lt;em&gt;that&lt;/em&gt;, then everything after that point is replaced by zeros.  This is the key bit that defeats the data leak.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But, why not just cut off the response at that point?  The problem is, we've already sent the headers of the response, and most likely, the response headers included a &lt;code&gt;Content-Length&lt;/code&gt;, so we need to emit that much data.&lt;/p&gt;

&lt;p&gt;One other thing. The &lt;code&gt;chunk&lt;/code&gt; that comes out of the &lt;code&gt;reader.read()&lt;/code&gt; method is an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer" rel="noopener noreferrer"&gt;&lt;code&gt;ArrayBuffer&lt;/code&gt;&lt;/a&gt;, and we need to search it to find the special sequences of bytes we're looking for.  There's no way to do this natively in JavaScript so I invented a function to search for a sequence of elements in an array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Find a sequence of elements in an array&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;seqFind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fromIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;fromIndex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;seqFind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's definitely ways to improve this, but for a quick exercise in what can be done with edge computing, it's really cool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fiddle.fastly.dev/fiddle/8a4a93fc" rel="noopener noreferrer"&gt;Check out my fiddle here&lt;/a&gt; and if you like, clone it and make your own version.&lt;/p&gt;

</description>
      <category>edgecompute</category>
      <category>security</category>
      <category>javascript</category>
    </item>
    <item>
      <title>JavaScript support hits 1.0 milestone on Compute@Edge</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Mon, 19 Dec 2022 16:26:51 +0000</pubDate>
      <link>https://dev.to/fastly/javascript-support-hits-10-milestone-on-computeedge-2n61</link>
      <guid>https://dev.to/fastly/javascript-support-hits-10-milestone-on-computeedge-2n61</guid>
      <description>&lt;p&gt;When Compute@Edge was launched, we talked about &lt;a href="https://www.fastly.com/blog/why-edge-compute-does-not-yet-support-javascript" rel="noopener noreferrer"&gt;why we didn’t fully support JavaScript&lt;/a&gt; at that time. Support &lt;a href="https://www.fastly.com/blog/compute-edge-now-supports-javascript" rel="noopener noreferrer"&gt;was added&lt;/a&gt; in July last year, and it has been an exciting journey to see what people have already built with the SDK at scale. With confidence in its stability we are proud to announce a 1.0 release of our JavaScript SDK. &lt;/p&gt;

&lt;p&gt;We listened to the community feedback, filled in feature gaps, and &lt;a href="https://github.com/fastly/js-compute-runtime/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;addressed many bugs&lt;/a&gt; in the SDK.  Not only that, we’ve also overhauled the &lt;a href="https://js-compute-reference-docs.edgecompute.app/" rel="noopener noreferrer"&gt;SDK reference docs&lt;/a&gt; making it easier for you to know what’s supported and how to implement the features. All the Fastly specific features of the JS SDK now have &lt;a href="https://js-compute-reference-docs.edgecompute.app/stable/classes/fastly_config_store.ConfigStore.html" rel="noopener noreferrer"&gt;interactive example applications&lt;/a&gt; in the documentation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F041admda5z1gqqqoh80z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F041admda5z1gqqqoh80z.png" alt="Interactive examples in docs" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've also been building out &lt;a href="https://developer.fastly.com/solutions/examples/javascript/" rel="noopener noreferrer"&gt;example code on our developer hub&lt;/a&gt; to showcase how to use JavaScript to address common use cases. Take a look at examples like &lt;a href="https://developer.fastly.com/solutions/examples/decorating-origin-requests-with-geoip" rel="noopener noreferrer"&gt;geo-tagging of requests&lt;/a&gt;, &lt;a href="https://developer.fastly.com/solutions/examples/no-origin-rum-logging" rel="noopener noreferrer"&gt;capturing and aggregating log data from clients&lt;/a&gt;, &lt;a href="https://developer.fastly.com/solutions/examples/normalize-requests" rel="noopener noreferrer"&gt;normalizing requests to improve cache efficiency&lt;/a&gt;, or &lt;a href="https://developer.fastly.com/solutions/examples/random-director-with-weighted-probability" rel="noopener noreferrer"&gt;load balancing across multiple backends&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Want to deploy a site you've built using a JavaScript-based web framework?  We got you - check out our tools and instructions for &lt;a href="https://www.fastly.com/blog/gatsby-on-the-edge-in-under-2-minutes-with-fastly" rel="noopener noreferrer"&gt;Gatsby&lt;/a&gt; and &lt;a href="https://www.fastly.com/blog/run-your-next-js-app-on-fastly" rel="noopener noreferrer"&gt;Next.JS&lt;/a&gt;.  &lt;a href="https://github.com/remix-run/remix/pull/4860" rel="noopener noreferrer"&gt;RemixJS is coming soon&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What if you have a dynamic backend in NodeJS running &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;ExpressJS&lt;/a&gt;?  It'd be cool if Compute@Edge offered Express-like abstractions, so you can write server apps on Fastly using familiar constructs like route handlers and middleware. No problem! Check out &lt;a href="https://expressly.edgecompute.app/" rel="noopener noreferrer"&gt;Expressly&lt;/a&gt;, our Express-alike server framework at the edge!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ejw1bny8lcivr0zrmjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ejw1bny8lcivr0zrmjw.png" alt="Expressly screenshot" width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are blogs and tutorials to inspire you if you're after something more advanced too: learn more about &lt;a href="https://developer.fastly.com/solutions/tutorials/ab-testing-edge-compute/" rel="noopener noreferrer"&gt;using JavaScript for A/B testing&lt;/a&gt;, or create a &lt;a href="https://developer.fastly.com/solutions/tutorials/waiting-room/" rel="noopener noreferrer"&gt;waiting room&lt;/a&gt; to handle traffic surges, for example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our support for JavaScript is unique
&lt;/h2&gt;

&lt;p&gt;In the serverless world, “cold starts” are the enemy. Even if a platform can run your code fast 99% of the time, an occasional 300ms delay while your app starts up can create debugging headaches, and these can easily add together in a microservices architecture and lead to much more frequent poor performance for end users.  At Fastly, we don't have cold starts because we don't do warm starts: your code starts from the same state on every request, and that startup takes microseconds. My colleague Lin Clark has explored &lt;a href="https://bytecodealliance.org/articles/making-javascript-run-fast-on-webassembly" rel="noopener noreferrer"&gt;how we do this over on the Bytecode Alliance blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Speaking of which, as a founding member of the &lt;a href="https://bytecodealliance.org/" rel="noopener noreferrer"&gt;Bytecode Alliance&lt;/a&gt;, we're building an ecosystem of languages that can run on the server using WebAssembly, using the &lt;a href="https://wasmtime.dev/" rel="noopener noreferrer"&gt;Wasmtime&lt;/a&gt; runtime.&lt;/p&gt;

&lt;p&gt;For JavaScript we use &lt;a href="https://spidermonkey.dev/" rel="noopener noreferrer"&gt;SpiderMonkey&lt;/a&gt; on top of Wasmtime, which provides spatial and temporal separation between concurrent tasks. Spatially, each request gets its own language runtime and memory heap. Temporarily, there is no state left over from previous requests. Starting from a fresh identical snapshot on every request makes debugging significantly easier. A bug in one task cannot affect subsequent or neighboring tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future of JavaScript on Compute@Edge
&lt;/h2&gt;

&lt;p&gt;We’ve solved the biggest security headaches of secure sandboxing, and have unmatched initialization performance of startup times, from cold, in the tens of microseconds. With the v1 release of our SDK we have achieved sufficient feature support in JavaScript while providing execution performance that is within a reasonable margin of modern JS JITs. But &lt;a href="https://bytecodealliance.org/articles/making-javascript-run-fast-on-webassembly" rel="noopener noreferrer"&gt;we're going to make it even faster&lt;/a&gt;, so stay tuned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not JUST JavaScript
&lt;/h2&gt;

&lt;p&gt;And because our platform doesn't rely on a JavaScript engine at its core, you're not locked into one language - write JavaScript if you want to, but  if you need the robustness of strict static typing and lower level memory management, you could just as well use &lt;a href="https://developer.fastly.com/learning/compute/rust/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt; or &lt;a href="https://developer.fastly.com/learning/compute/go/" rel="noopener noreferrer"&gt;Go&lt;/a&gt; instead.  And developers outside of Fastly have even made SDKs for languages we don't officially support yet: we celebrate them and &lt;a href="https://developer.fastly.com/learning/compute/custom/" rel="noopener noreferrer"&gt;showcase them on our developer hub&lt;/a&gt;.  All languages that run on Compute@Edge are equals on our platform.  &lt;/p&gt;

&lt;p&gt;We love to see what our customers build. Find us on twitter at &lt;a href="https://twitter.com/fastlydevs" rel="noopener noreferrer"&gt;@fastlydevs&lt;/a&gt; or tweet with &lt;code&gt;#builtonfastly&lt;/code&gt; and let us know!&lt;/p&gt;

</description>
      <category>news</category>
      <category>javascript</category>
      <category>fastly</category>
    </item>
    <item>
      <title>We won best dev portal!</title>
      <dc:creator>Andrew Betts</dc:creator>
      <pubDate>Fri, 16 Dec 2022 17:08:28 +0000</pubDate>
      <link>https://dev.to/fastly/we-won-best-dev-portal-58e0</link>
      <guid>https://dev.to/fastly/we-won-best-dev-portal-58e0</guid>
      <description>&lt;p&gt;Yesterday the Fastly developer hub &lt;code&gt;developer.fastly.com&lt;/code&gt; won &lt;strong&gt;Best Onboarding Experience&lt;/strong&gt; at the &lt;a href="https://devportalawards.org/" rel="noopener noreferrer"&gt;2022 Dev Portal Awards&lt;/a&gt;!  &lt;/p&gt;

&lt;p&gt;One of the jurors said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;They really want to get you started fast and without friction. Very well done, provides a lot of helpful resources. Testing options that you can use immediately.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the other comments was my favourite though:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Really impressed that Fastly finally did the interactive tutorial that Stripe was known for, can try and see an API that is quite complicated, this is not easy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We were nominated in a group of 12, some of the best developer experiences around, and we were honoured to be in such auspicious company.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1jwdzxj7abelcglcg6w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1jwdzxj7abelcglcg6w.png" alt="Image description" width="720" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's so important to us to have respect for our developer community. The docs and tools we offer to our developers should be as good as or better than the docs we rely on ourselves.  That means comprehensive coverage - if you can use it, we should document it - and tools that let you try things out as easily as possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyll0clid5tepo9i5h8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyll0clid5tepo9i5h8g.png" alt="Screenshot" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Describing our abstractions is also a great opportunity to recognise rough edges and feed improvements back into our product development.&lt;/p&gt;

&lt;p&gt;And beyond providing information and helping to shorten the path from idea to execution, we see one of the key goals of our developer hub to be to inspire.  There's a balance to be struck - we want to help people understand the art of the possible but we also know the very best ideas for how to use Fastly and what to use Fastly for are going to come from the developer community.  So we try to inspire but also to empower people to experiment and play with their own ideas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ufvvu9q6hsyr0ohq8wv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ufvvu9q6hsyr0ohq8wv.png" alt="Screenshot" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In all of this we have ourselves been inspired by some spectacular sites that we use every day: Stripe, MDN, Eventbrite, Google, and Twilio have all inspired elements of the Fastly developer site.&lt;/p&gt;

&lt;p&gt;Finally, I'm absurdly proud of the six people across three continents that work incredibly hard to make the developer hub what it is.  &lt;a href="https://journiedotcom.glitch.me/#/" rel="noopener noreferrer"&gt;Journie&lt;/a&gt;, &lt;a href="https://www.chrispoole.com" rel="noopener noreferrer"&gt;Chris&lt;/a&gt;, &lt;a href="https://mobile.twitter.com/katsuyukiomuro" rel="noopener noreferrer"&gt;Kats&lt;/a&gt;, &lt;a href="https://www.integralist.co.uk/" rel="noopener noreferrer"&gt;Mark&lt;/a&gt;, &lt;a href="https://twitter.com/kailanblanks" rel="noopener noreferrer"&gt;Kailan&lt;/a&gt;, and &lt;a href="https://doramilitaru.com/" rel="noopener noreferrer"&gt;Dora&lt;/a&gt;, have done a fantastic job, and it's really lovely to be able to recognise the people behind the webpages.&lt;/p&gt;

&lt;p&gt;And of course thank you to our developer community, you're the people we do this for, and there's a reason we put a feedback form on every single page of the devhub, because we love talking to you and we love it when you talk to us.&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>documentation</category>
      <category>news</category>
      <category>community</category>
    </item>
  </channel>
</rss>
