
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Sun, 05 Apr 2026 16:42:09 GMT</lastBuildDate>
        <item>
            <title><![CDATA[R2 SQL: a deep dive into our new distributed query engine]]></title>
            <link>https://blog.cloudflare.com/r2-sql-deep-dive/</link>
            <pubDate>Thu, 25 Sep 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ R2 SQL provides a built-in, serverless way to run ad-hoc analytic queries against your R2 Data Catalog. This post dives deep under the Iceberg into how we built this distributed engine. ]]></description>
            <content:encoded><![CDATA[ <p>How do you run SQL queries over petabytes of data… without a server?</p><p>We have an answer for that: <a href="https://developers.cloudflare.com/r2-sql/"><u>R2 SQL</u></a>, a serverless query engine that can sift through enormous datasets and return results in seconds.</p><p>This post details the architecture and techniques that make this possible. We'll walk through our Query Planner, which uses <a href="https://developers.cloudflare.com/r2/data-catalog/"><u>R2 Data Catalog</u></a> to prune terabytes of data before reading a single byte, and explain how we distribute the work across Cloudflare’s <a href="https://www.cloudflare.com/network"><u>global network</u></a>, <a href="https://developers.cloudflare.com/workers/"><u>Workers</u></a> and <a href="https://www.cloudflare.com/developer-platform/products/r2/"><u>R2</u></a> for massively parallel execution.</p>
    <div>
      <h3>From catalog to query</h3>
      <a href="#from-catalog-to-query">
        
      </a>
    </div>
    <p>During Developer Week 2025, we <a href="https://blog.cloudflare.com/r2-data-catalog-public-beta/"><u>launched</u></a> R2 Data Catalog, a managed <a href="https://iceberg.apache.org/"><u>Apache Iceberg</u></a> catalog built directly into your Cloudflare R2 bucket. Iceberg is an open table format that provides critical database features like transactions and schema evolution for petabyte-scale <a href="https://www.cloudflare.com/learning/cloud/what-is-object-storage/">object storage</a>. It gives you a reliable catalog of your data, but it doesn’t provide a way to query it.</p><p>Until now, reading your R2 Data Catalog required setting up a separate service like <a href="https://spark.apache.org/"><u>Apache Spark</u></a> or <a href="https://trino.io/"><u>Trino</u></a>. Operating these engines at scale is not easy: you need to provision clusters, manage resource usage, and be responsible for their availability, none of which contributes to the primary goal of getting value from your data.</p><p><a href="https://developers.cloudflare.com/r2-sql/"><u>R2 SQL</u></a> removes that step entirely. It’s a serverless query engine that executes retrieval SQL queries against your Iceberg tables, right where your data lives.</p>
    <div>
      <h3>Designing a query engine for petabytes</h3>
      <a href="#designing-a-query-engine-for-petabytes">
        
      </a>
    </div>
    <p>Object storage is fundamentally different from a traditional database’s storage. A database is structured by design; R2 is an ocean of objects, where a single logical table can be composed of potentially millions of individual files, large and small, with more arriving every second.</p><p>Apache Iceberg provides a powerful layer of logical organization on top of this reality. It works by managing the table's state as an immutable series of snapshots, creating a reliable, structured view of the table by manipulating lightweight metadata files instead of rewriting the data files themselves.</p><p>However, this logical structure doesn't change the underlying physical challenge: an efficient query engine must still find the specific data it needs within that vast collection of files, and this requires overcoming two major technical hurdles:</p><p><b>The I/O problem</b>: A core challenge for query efficiency is minimizing the amount of data read from storage. A brute-force approach of reading every object is simply not viable. The primary goal is to read only the data that is absolutely necessary.</p><p><b>The Compute problem</b>: The amount of data that does need to be read can still be enormous. We need a way to give the right amount of compute power to a query, which might be massive, for just a few seconds, and then scale it down to zero instantly to avoid waste.</p><p>Our architecture for R2 SQL is designed to solve these two problems with a two-phase approach: a <b>Query Planner</b> that uses metadata to intelligently prune the search space, and a <b>Query Execution</b> system that distributes the work across Cloudflare's global network to process the data in parallel.</p>
    <div>
      <h2>Query Planner</h2>
      <a href="#query-planner">
        
      </a>
    </div>
    <p>The most efficient way to process data is to avoid reading it in the first place. This is the core strategy of the R2 SQL Query Planner. Instead of exhaustively scanning every file, the planner makes use of the metadata structure provided by R2 Data Catalog to prune the search space, that is, to avoid reading huge swathes of data irrelevant to a query.</p><p>This is a top-down investigation where the planner navigates the hierarchy of Iceberg metadata layers, using <b>stats</b> at each level to build a fast plan, specifying exactly which byte ranges the query engine needs to read.</p>
    <div>
      <h3>What do we mean by “stats”?</h3>
      <a href="#what-do-we-mean-by-stats">
        
      </a>
    </div>
    <p>When we say the planner uses "stats" we are referring to summary metadata that Iceberg stores about the contents of the data files. These statistics create a coarse map of the data, allowing the planner to make decisions about which files to read, and which to ignore, without opening them.</p><p>There are two primary levels of statistics the planner uses for pruning:</p><p><b>Partition-level stats</b>: Stored in the Iceberg manifest list, these stats describe the range of partition values for all the data in a given Iceberg manifest file. For a partition on <code>day(event_timestamp)</code>, this would be the earliest and latest day present in the files tracked by that manifest.</p><p><b>Column-level stats</b>: Stored in the manifest files, these are more granular stats about each individual data file. Data files in R2 Data Catalog are formatted using the <a href="https://parquet.apache.org/"><u>Apache Parquet</u></a>. For every column of a Parquet file, the manifest stores key information like:</p><ul><li><p>The minimum and maximum values. If a query asks for <code>http_status = 500</code>, and a file’s stats show its <code>http_status</code> column has a min of 200 and a max of 404, that entire file can be skipped.</p></li><li><p>A count of null values. This allows the planner to skip files when a query specifically looks for non-null values (e.g.,<code> WHERE error_code IS NOT NULL</code>) and the file's metadata reports that all values for <code>error_code</code> are null.</p></li></ul><p>Now, let's see how the planner uses these stats as it walks through the metadata layers.</p>
    <div>
      <h3>Pruning the search space</h3>
      <a href="#pruning-the-search-space">
        
      </a>
    </div>
    <p>The pruning process is a top-down investigation that happens in three main steps:</p><ol><li><p><b>Table metadata and the current snapshot</b></p></li></ol><p>The planner begins by asking the catalog for the location of the current table metadata. This is a JSON file containing the table's current schema, partition specs, and a log of all historical snapshots. The planner then fetches the latest snapshot to work with.</p><p>2. <b>Manifest list and partition pruning</b></p><p>The current snapshot points to a single Iceberg manifest list. The planner reads this file and uses the partition-level stats for each entry to perform the first, most powerful pruning step, discarding any manifests whose partition value ranges don't satisfy the query. For a table partitioned by <code>day(event_timestamp</code>), the planner can use the min/max values in the manifest list to immediately discard any manifests that don't contain data for the days relevant to the query.</p><p>3.<b> Manifests and file-level pruning</b></p><p>For the remaining manifests, the planner reads each one to get a list of the actual Parquet data files. These manifest files contain more granular, column-level stats for each individual data file they track. This allows for a second pruning step, discarding entire data files that cannot possibly contain rows matching the query's filters.</p><p>4. <b>File row-group pruning</b></p><p>Finally, for the specific data files that are still candidates, the Query Planner uses statistics stored inside Parquet file's footers to skip over entire row groups.</p><p>The result of this multi-layer pruning is a precise list of Parquet files, and of row groups within those Parquet files. These become the query work units that are dispatched to the Query Execution system for processing.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7GKvgbex2vhIBqQ1G5UFjQ/2a99db7ae786b8e22a326bac0c9037d9/1.png" />
          </figure>
    <div>
      <h3>The Planning pipeline</h3>
      <a href="#the-planning-pipeline">
        
      </a>
    </div>
    <p>In R2 SQL, the multi-layer pruning we've described so far isn't a monolithic process. For a table with millions of files, the metadata can be too large to process before starting any real work. Waiting for a complete plan would introduce significant latency.</p><p>Instead, R2 SQL treats planning and execution together as a concurrent pipeline. The planner's job is to produce a stream of work units for the executor to consume as soon as they are available.</p><p>The planner’s investigation begins with two fetches to get a map of the table's structure: one for the table’s snapshot and another for the manifest list.</p>
    <div>
      <h4>Starting execution as early as possible</h4>
      <a href="#starting-execution-as-early-as-possible">
        
      </a>
    </div>
    <p>From that point on, the query is processed in a streaming fashion. As the Query Planner reads through the manifest files and subsequently the data files they point to and prunes them, it immediately emits any matching data files/row groups as work units to the execution queue.</p><p>This pipeline structure ensures the compute nodes can begin the expensive work of data I/O almost instantly, long before the planner has finished its full investigation.</p><p>On top of this pipeline model, the planner adds a crucial optimization: <b>deliberate ordering</b>. The manifest files are not streamed in an arbitrary sequence. Instead, the planner processes them in an order matching by the query's <code>ORDER BY</code> clause, guided by the metadata stats. This ensures that the data most likely to contain the desired results is processed first.</p><p>These two concepts work together to address query latency from both ends of the query pipeline.</p><p>The streamed planning pipeline lets us start crunching data as soon as possible, minimizing the delay before the first byte is processed. At the other end of the pipeline, the deliberate ordering of that work lets us finish early by finding a definitive result without scanning the entire dataset.</p><p>The next section explains the mechanics behind this "finish early" strategy.</p>
    <div>
      <h4>Stopping early: how to finish without reading everything</h4>
      <a href="#stopping-early-how-to-finish-without-reading-everything">
        
      </a>
    </div>
    <p>Thanks to the Query Planner streaming work units in an order matching the <code>ORDER BY </code>clause, the Query Execution system first processes the data that is most likely to be in the final result set.</p><p>This prioritization happens at two levels of the metadata hierarchy:</p><p><b>Manifest ordering</b>: The planner first inspects the manifest list. Using the partition stats for each manifest (e.g., the latest timestamp in that group of files), it decides which entire manifest files to stream first.</p><p><b>Parquet file ordering</b>: As it reads each manifest, it then uses the more granular column-level stats to decide the processing order of the individual Parquet files within that manifest.</p><p>This ensures a constantly prioritized stream of work units is sent to the execution engine. This prioritized stream is what allows us to stop the query early.</p><p>For instance, with a query like ... <code>ORDER BY timestamp DESC LIMIT 5</code>, as the execution engine processes work units and sends back results, the planner does two things concurrently:</p><p>It maintains a bounded heap of the best 5 results seen so far, constantly comparing new results to the oldest timestamp in the heap.</p><p>It keeps a "high-water mark" on the stream itself. Thanks to the metadata, it always knows the absolute latest timestamp of any data file that has not yet been processed.</p><p>The planner is constantly comparing the state of the heap to the water mark of the remaining stream. The moment the oldest timestamp in our Top 5 heap is newer than the high-water mark of the remaining stream, the entire query can be stopped.</p><p>At that point, we can prove no remaining work unit could possibly contain a result that would make it into the top 5. The pipeline is halted, and a complete, correct result is returned to the user, often after reading only a fraction of the potentially matching data.</p><p>Currently, R2 SQL supports ordering on columns that are part of the table's partition key only. This is a limitation we are working on lifting in the future.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5qN9TeEuRZJIidYXFictG/8a55cc6088be3abdc3b27878daa76e40/image4.png" />
          </figure>
    <div>
      <h3>Architecture</h3>
      <a href="#architecture">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3wkvnT24y5E0k5064cqu0T/939402d16583647986eec87617379900/image3.png" />
          </figure>
    <div>
      <h2>Query Execution</h2>
      <a href="#query-execution">
        
      </a>
    </div>
    <p>Query Planner streams the query work in bite-sized pieces called row groups. A single Parquet file usually contains multiple row groups, but most of the time only a few of them contain relevant data. Splitting query work into row groups allows R2 SQL to only read small parts of potentially multi-GB Parquet files.</p><p>The server that receives the user’s request and performs query planning assumes the role of query coordinator. It distributes the work across query workers and aggregates results before returning them to the user.</p><p>Cloudflare’s network is vast, and many servers can be in maintenance at the same time. The query coordinator contacts Cloudflare’s internal API to make sure only healthy, fully functioning servers are picked for query execution. Connections between coordinator and query worker go through <a href="https://www.cloudflare.com/en-gb/application-services/products/argo-smart-routing/"><u>Cloudflare Argo Smart Routing</u></a> to ensure fast, reliable connectivity.</p><p>Servers that receive query execution requests from the coordinator assume the role of query workers. Query workers serve as a point of horizontal scalability in R2 SQL. With a higher number of query workers, R2 SQL can process queries faster by distributing the work among many servers. That’s especially true for queries covering large amounts of files.</p><p>Both the coordinator and query workers run on Cloudflare’s distributed network, ensuring R2 SQL has plenty of compute power and I/O throughput to handle analytical workloads.</p><p>Each query worker receives a batch of row groups from the coordinator as well as an SQL query to run on it. Additionally, the coordinator sends serialized metadata about Parquet files containing the row groups. Thanks to that, query workers know exact byte offsets where each row group is located in the Parquet file without the need to read this information from R2.</p>
    <div>
      <h3>Apache DataFusion</h3>
      <a href="#apache-datafusion">
        
      </a>
    </div>
    <p>Internally, each query worker uses <a href="https://github.com/apache/datafusion"><u>Apache DataFusion</u></a> to run SQL queries against row groups. DataFusion is an open-source analytical query engine written in Rust. It is built around the concept of partitions. A query is split into multiple concurrent independent streams, each working on its own partition of data.</p><p>Partitions in DataFusion are similar to partitions in Iceberg, but serve a different purpose. In Iceberg, partitions are a way to physically organize data on object storage. In DataFusion, partitions organize in-memory data for query processing. While logically they are similar – rows grouped together based on some logic – in practice, a partition in Iceberg doesn’t always correspond to a partition in DataFusion.</p><p>DataFusion partitions map perfectly to the R2 SQL query worker’s data model because each row group can be considered its own independent partition. Thanks to that, each row group is processed in parallel.</p><p>At the same time, since row groups usually contain at least 1000 rows, R2 SQL benefits from vectorized execution. Each DataFusion partition stream can execute the SQL query on multiple rows in one go, amortizing the overhead of query interpretation.</p><p>There are two ends of the spectrum when it comes to query execution: processing all rows sequentially in one big batch and processing each individual row in parallel. Sequential processing creates a so-called “tight loop”, which is usually more CPU cache friendly. In addition to that, we can significantly reduce interpretation overhead, as processing a large number of rows at a time in batches means that we go through the query plan less often. Completely parallel processing doesn’t allow us to do these things, but makes use of multiple CPU cores to finish the query faster.</p><p>DataFusion’s architecture allows us to achieve a balance on this scale, reaping benefits from both ends. For each data partition, we gain better CPU cache locality and amortized interpretation overhead. At the same time, since many partitions are processed in parallel, we distribute the workload between multiple CPUs, cutting the execution time further.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2Tis1F5C1x3x6sIyJLL8ju/aae094818b1b7f6f8d6f857305948fbd/image1.png" />
          </figure><p>In addition to the smart query execution model, DataFusion also provides first-class Parquet support.</p><p>As a file format, Parquet has multiple optimizations designed specifically for query engines. Parquet is a column-based format, meaning that each column is physically separated from others. This separation allows better compression ratios, but it also allows the query engine to read columns selectively. If the query only ever uses five columns, we can only read them and skip reading the remaining fifty. This massively reduces the amount of data we need to read from R2 and the CPU time spent on decompression.</p><p>DataFusion does exactly that. Using R2 ranged reads, it is able to read parts of the Parquet files containing the requested columns, skipping the rest.</p><p>DataFusion’s optimizer also allows us to push down any filters to the lowest levels of the query plan. In other words, we can apply filters right as we are reading values from Parquet files. This allows us to skip materialization of results we know for sure won’t be returned to the user, cutting the query execution time further.</p>
    <div>
      <h3>Returning query results</h3>
      <a href="#returning-query-results">
        
      </a>
    </div>
    <p>Once the query worker finishes computing results, it returns them to the coordinator through <a href="https://grpc.io/"><u>the gRPC protocol</u></a>.</p><p>R2 SQL uses <a href="https://arrow.apache.org/"><u>Apache Arrow</u></a> for internal representation of query results. Arrow is an in-memory format that efficiently represents arrays of structured data. It is also used by DataFusion during query execution to represent partitions of data.</p><p>In addition to being an in-memory format, Arrow also defines the <a href="https://arrow.apache.org/docs/format/Columnar.html#format-ipc"><u>Arrow IPC</u></a> serialization format. Arrow IPC isn’t designed for long-term storage of the data, but for inter-process communication, which is exactly what query workers and the coordinator do over the network. The query worker serializes all the results into the Arrow IPC format and embeds them into the gRPC response. The coordinator in turn deserializes results and can return to working on Arrow arrays.</p>
    <div>
      <h2>Future plans</h2>
      <a href="#future-plans">
        
      </a>
    </div>
    <p>While R2 SQL is currently quite good at executing filter queries, we also plan to rapidly add new capabilities over the coming months. This includes, but is not limited to, adding:</p><ul><li><p>Support for complex aggregations in a distributed and scalable fashion;</p></li><li><p>Tools to help provide visibility in query execution to help developers improve performance;</p></li><li><p>Support for many of the configuration options Apache Iceberg supports.</p></li></ul><p>In addition to that, we have plans to improve our developer experience by allowing users to query their R2 Data Catalogs using R2 SQL from the Cloudflare Dashboard.</p><p>Given Cloudflare’s distributed compute, network capabilities, and ecosystem of developer tools, we have the opportunity to build something truly unique here. We are exploring different kinds of indexes to make R2 SQL queries even faster and provide more functionality such as full text search, geospatial queries, and more. </p>
    <div>
      <h2>Try it now!</h2>
      <a href="#try-it-now">
        
      </a>
    </div>
    <p>It’s early days for R2 SQL, but we’re excited for users to get their hands on it. R2 SQL is available in open beta today! Head over to our<a href="https://developers.cloudflare.com/r2-sql/get-started/"> <u>getting started guide</u></a> to learn how to create an end-to-end data pipeline that processes and delivers events to an R2 Data Catalog table, which can then be queried with R2 SQL.</p><p>
We’re excited to see what you build! Come share your feedback with us on our<a href="http://discord.cloudflare.com/"> <u>Developer Discord</u></a>.</p><div>
  
</div><p></p> ]]></content:encoded>
            <category><![CDATA[R2]]></category>
            <category><![CDATA[Birthday Week]]></category>
            <category><![CDATA[Data]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <category><![CDATA[Edge Computing]]></category>
            <category><![CDATA[Rust]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[SQL]]></category>
            <guid isPermaLink="false">7znvjodLkg1AxYlR992it2</guid>
            <dc:creator>Yevgen Safronov</dc:creator>
            <dc:creator>Nikita Lapkov</dc:creator>
            <dc:creator>Jérôme Schneider</dc:creator>
        </item>
        <item>
            <title><![CDATA[SVG support in Cloudflare Images]]></title>
            <link>https://blog.cloudflare.com/svg-support-in-cloudflare-images/</link>
            <pubDate>Wed, 21 Sep 2022 14:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare Images now supports storing and delivering SVG files ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Cloudflare Images was announced one year ago <a href="/announcing-cloudflare-images/">on this very blog</a> to help you solve the problem of delivering images in the right size, right quality and fast. Very fast.</p><p>It doesn’t really matter if you only run a personal blog, or a portal with thousands of vendors and millions of end-users. Doesn’t matter if you need one hundred images to be served one thousand times each at most, or if you deal with tens of millions of new, unoptimized, images that you deliver billions of times per month.</p><p>We want to remove the complexity of dealing with the need to store, to process, resize, re-encode and serve the images using multiple platforms and vendors.</p><p>At the time we wrote:</p><blockquote><p><i>Images is a single product that stores, resizes, optimizes and serves images. We built Cloudflare Images, so customers of all sizes can build a scalable and affordable image pipeline in minutes.</i></p></blockquote><p>We supported the most common formats, such as JPG, WebP, PNG and GIF.</p><p>We did not feel the need to support SVG files. SVG files are inherently scalable, so there is nothing to resize on the server side before serving them to your audience. One can even argue that SVG files are documents that can generate images through mathematical formulas of vectors and nodes, but are not images <i>per se.</i></p><p>There was also the clear notion that SVG files were a potential risk due to known and <a href="https://www.fortinet.com/blog/threat-research/scalable-vector-graphics-attack-surface-anatomy">well documented</a> vulnerabilities. We knew we could do something from the security angle, but still, why go through that workload if it <i>didn’t make sense</i> in the first place to consider an SVG as a supported format.</p><p>Not supporting SVG files, though, did bring a set of challenges to an increasing number of our customers. <a href="https://w3techs.com/technologies/details/im-svg">Some stats already show that around 50% of websites serve SVG files</a>, which matches the pulse we got from talking with many of you, customers and community.</p><p>If you relied on SVGs, you had to select a second storage location or a second image platform elsewhere. That commonly resulted in an egress fee when serving an uncached file from that source, and it goes against what we want for our product: one image pipeline to cover all your needs.</p><p>We heard loud and clear, and starting from today, you can store and serve SVG files, safely, with Cloudflare Images.</p>
    <div>
      <h3>SVG, what is so special about them?</h3>
      <a href="#svg-what-is-so-special-about-them">
        
      </a>
    </div>
    <p>The Scalable Vector Graphics file type is great for serving all kinds of illustrations, charts, logos, and icons.</p><p>SVG files don't represent images as pixels, but as geometric shapes (lines, arcs, polygons) that can be drawn with perfect sharpness at any resolution.</p><p>Let’s use now a complex image as an example, filled with more than four hundred paths and ten thousand nodes:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4ruJDon2gjvBXwHi9DGsA7/997727a99a00188695871c37f08adf46/uHWAmWDUYVNmDByskHnBsSf_-poXNMAz7sxTw-bjNYHldqbU5ecTj_upSCKIHoXRnolnrlpPqvyDbBray-TaRkDJcGOO9CKQUdY3CpvwmaNn0rRkqqnPLAJJaE0D.png" />
            
            </figure><p>Contrary to the bitmaps where pixels arrange together to create the visual perception of an image to the human eye, that vector image can be resized with no quality loss. That happens because resizing that SVG to 300% of its original size is redefining the size of the vectors to 300%, not expanding pixels to 300%.</p><p>This becomes evident when we’re dealing with small resolution images.</p><p>Here is the 100px width SVG from the Toroid shown above:</p><p><img src="http://staging.blog.mrk.cfdata.org/content/images/2022/09/Toroid.svg" /></p><p>and the correspondent 100 pixels width PNG:</p><p><img src="http://staging.blog.mrk.cfdata.org/content/images/2022/09/image3-18.png" /></p><p>Now here is the same SVG with the HTML width attribute set at 300px:</p><p><img src="http://staging.blog.mrk.cfdata.org/content/images/2022/09/Toroid.svg" /></p><p>and the same PNG you saw before, but, upscaled by 3x, so the width is also 300px:</p><p><img src="http://staging.blog.mrk.cfdata.org/content/images/2022/09/unnamed.png" /></p><p>The visual quality loss on the PNG is obvious when it gets scaled up.</p><p>Keep in mind: The Toroid shown above is stored in an SVG file of 142Kb. And that is a very complex and heavy SVG file already.</p><p>Now, if you do want to display a PNG with an original width of 1024px to present a high quality image of the same Toroid above, the size will become an issue:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1haST58mkysZs17Fn2dNv4/d297d11a7aef91c24c8fcda6a89ddf4f/unnamed--1-.png" />
            
            </figure><p>The new 1024px PNG, however, weighs 344 KB. That’s about 2.4 times the weight of the unique SVG that you could use in any size.</p><p>Think about the storage and bandwidth savings when all you need to do with an SVG, to get the exact same displayed image is use a <code>width=”1024”</code> in your HTML. It requires less than half of the kilobytes used on the PNG.</p><p>Couple all of this with the flexibility of using attributes like <code>viewbox</code> in your HTML code, and you can pan, zoom, crop, scale, all without ever needing anything other than the one and original SVG file.</p><p>Here’s an example of an SVG being resized on the client side, with no visual quality loss:</p><div></div>
<p></p><p>Let’s do a quick summary of what we covered so far: SVG files are wonderful for vector images like illustrations, charts, logos, and are infinitely scalable with no need to resize on the server side;</p><p>the same generated image, but on a bitmap is either heavier than the SVG when used in high resolutions, or with very noticeable loss of visual quality when scaled up from a lower resolution.</p>
    <div>
      <h3>So, what are the downsides of using SVG files?</h3>
      <a href="#so-what-are-the-downsides-of-using-svg-files">
        
      </a>
    </div>
    <p>SVG files aren't just images. They are XML-based documents that are as powerful as HTML pages. They can contain arbitrary JavaScript, fetch external content from other URLs or embed HTML elements. This gives SVG files much more power than expected from a simple image.</p><p>Throughout the years, numerous exploits have been known, identified and corrected.</p><p>Some old attacks were very rudimentary, yet effective. The famous <a href="https://en.wikipedia.org/wiki/Billion_laughs_attack">Billion Laughs</a> exploited how <a href="https://www.w3resource.com/xml/entities.php">XML uses Entities and declares them in the Document Type Definition</a>, and how it handles recursion.</p><p>Entities can be something as simple as a declaration of a text string, or a nested reference to other previous entities.</p><p>If you defined a first entity with a simple string, and then created a second entity calling 10 times the first one, and then a third entity calling 10 times the second one up until a 10th one of the same kind, you were requiring a parser to generate an output of a billion strings as defined on the very simple first entity. This would most commonly exhaust resources on the server parsing the XML, and form a <a href="https://www.cloudflare.com/en-gb/learning/ddos/what-is-a-ddos-attack/">DoS</a>. While that particular limitation from the XML parsing got widely addressed through XML parser memory caps and lazy loading of entities, more complex attacks became a regular thing in recent years.</p><p>The common themes in these more recent attacks have been <a href="https://www.cloudflare.com/learning/security/how-to-prevent-xss-attacks/">XSS (cross-site-scripting)</a> and foreign objects referenced in the XML content. In both cases, using SVG inside  tags in your HTML is an invitation for any ill-intended file to reach your end-users. So, what exactly can we do about it and make you trust any SVG file you serve?</p>
    <div>
      <h3>The SVG filter</h3>
      <a href="#the-svg-filter">
        
      </a>
    </div>
    <p>We've developed a filter that simplifies SVG files to only features used for images, so that serving SVG images from any source is just as safe as serving a JPEG or PNG, while preserving SVG's vector graphics capabilities.</p><ul><li><p>We remove scripting. This prevents SVG files from being used for cross-site scripting attacks. Although browsers don't allow scripts in , they would run scripts when SVG files are opened directly as a top-level document.</p></li><li><p>We remove hyperlinks to other documents. This makes SVG files less attractive for SEO spam and phishing.</p></li><li><p>We remove references to cross-origin resources. This stops 3rd parties from tracking who is viewing the image.</p></li></ul><p>What's left is just an image.</p><p>SVG files can also contain embedded images in other formats, like JPEG and PNG, in the form of <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs">Data URLs</a>. We treat these embedded images just like other images that we process, and optimize them too. We don't support SVG files embedded in SVG recursively, though. It does open the door to recursive parsing leading to resource exhaustion on the parser. While the most common browsers are already limiting SVG recursion to one level, the potential to exploit that door led us to not include, at least for now, this capability on our filter.</p><p>We do set Content-Security-Policy (CSP) headers in all our HTTP response headers to disable unwanted features, and that alone acts as first defense, but filtering acts in more depth in case these headers are lost (e.g. if the image was saved as a file and served elsewhere).</p><p>Our tool is <a href="https://github.com/cloudflare/svg-hush">open-source</a>. It's written in Rust and can filter SVG files in a streaming fashion without buffering, so it's fast enough for filtering on the fly.</p><p>The SVG format is pretty complex, with lots of features. If there is safe SVG functionality that we don't support yet, you can report issues and contribute to development of the filter.</p><p>You can see how the tool actually works by looking at the tests folder in the open-source repository,  where a sample unfiltered XML and the already filtered version are present.</p><p>Here’s how a diff of those files looks like:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/61edBaHRCc923sZoBnknKB/dd590659eaab4dafb9c637434e3411a4/image5-8.png" />
            
            </figure><p>Removed are the external references, foreignObjects and any other potential threats.</p>
    <div>
      <h3>How you can use SVG files in Cloudflare Images</h3>
      <a href="#how-you-can-use-svg-files-in-cloudflare-images">
        
      </a>
    </div>
    <p>Starting now you can upload SVG files to Cloudflare Images and serve them at will. Uploading the images can be done like for any other supported format, <a href="https://developers.cloudflare.com/images/cloudflare-images/upload-images/dashboard-upload/">via UI</a> or <a href="https://developers.cloudflare.com/images/cloudflare-images/upload-images/upload-via-url/">API</a>.</p><div></div><p>Variants, <a href="https://developers.cloudflare.com/images/cloudflare-images/transform/resize-images/">named</a> or <a href="https://developers.cloudflare.com/images/cloudflare-images/transform/flexible-variants/">flexible</a>, are intended to transform bitmap (raster) images into whatever size you want to serve them.</p><p>SVG files, as vector images, do not require resizing inside the Images pipeline.</p><p>This results in a banner with the following message when you’re previewing an SVG in the UI:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7DBn12LoNf1pQdIV0yxlN3/737bebe7ab5bb17f2883448aaea23524/image1-30.png" />
            
            </figure><p>And as a result, all variants listed will show the exact same image in the exact same dimensions.</p><p>Because an image is worth a thousand words, especially when trying to describe behaviors, here is what will it look like if you scroll through the variants preview:</p><div></div><p>With Cloudflare Images you do get a default Public Variant listed when you start using the product, and so you can immediately start serving your SVG files using it, just like this:</p><p><a href="https://imagedelivery.net/">https://imagedelivery.net/</a>&lt;your_account_hash&gt;/&lt;your_SVG_ID&gt;/public</p><p>And, as shown from above, you can use any of your variant names to serve the image, as it won’t affect the output at all.</p><p>If you’re an Image Resizing customer, you can also benefit from serving your files with our tool. Make sure you head to the <a href="https://developers.cloudflare.com/images/image-resizing/">Developer Documentation</a> pages to see how.</p>
    <div>
      <h3>What’s next?</h3>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>You can subscribe to Cloudflare Images <a href="https://dash.cloudflare.com/?to=/:account/images">directly in the dashboard</a>, and starting from today you can use the product to store and serve SVG files.</p><p>If you want to contribute to further developments of the filtering too and help expand its abilities, check out our <a href="https://github.com/cloudflare/svg-hush">SVG-Hush Tool repo</a>.</p><p>You can also connect directly with the team in our <a href="https://discord.com/invite/cloudflaredev">Cloudflare Developers Discord Server</a>.</p> ]]></content:encoded>
            <category><![CDATA[GA Week]]></category>
            <category><![CDATA[General Availability]]></category>
            <category><![CDATA[Cloudflare Images]]></category>
            <guid isPermaLink="false">5Z8tlaSgZifZHEK46BkW2r</guid>
            <dc:creator>Paulo Costa</dc:creator>
            <dc:creator>Yevgen Safronov</dc:creator>
            <dc:creator>Kornel Lesiński</dc:creator>
        </item>
        <item>
            <title><![CDATA[Announcing the Cloudflare Images Sourcing Kit]]></title>
            <link>https://blog.cloudflare.com/cloudflare-images-sourcing-kit/</link>
            <pubDate>Fri, 13 May 2022 12:59:25 GMT</pubDate>
            <description><![CDATA[ Migrating millions of images into Cloudflare is now simple, fast and at the distance of a few clicks. The new Cloudflare Images Sourcing Kit 
Allows you to define your image sources, reuse them when you need to add new images or refresh existing ones ]]></description>
            <content:encoded><![CDATA[ <p></p><p>When we announced <a href="/announcing-cloudflare-images-beta/">Cloudflare Images to the world</a>, we introduced a way to store images within the product and help customers move away from the egress fees met when using remote sources for their deliveries via Cloudflare.</p><p>To <a href="https://www.cloudflare.com/products/cloudflare-images/">store the images in Cloudflare</a>, customers can upload them <a href="https://developers.cloudflare.com/images/cloudflare-images/upload-images/dashboard-upload/">via UI</a> with a simple drag and drop, or <a href="https://developers.cloudflare.com/images/cloudflare-images/api-request/">via API</a> for scenarios with a high number of objects for which scripting their way through the upload process makes more sense.</p><p>To create flexibility on how to import the images, we’ve recently also included the ability to <a href="https://developers.cloudflare.com/images/cloudflare-images/upload-images/upload-via-url/">upload via URL</a> or <a href="https://developers.cloudflare.com/images/cloudflare-images/upload-images/custom-id/">define custom names and paths for your images</a> to allow a simple mapping between customer repositories and the objects in Cloudflare. It's also possible to <a href="https://developers.cloudflare.com/images/cloudflare-images/serve-images/#serving-images-from-custom-domains">serve from a custom hostname</a> to create flexibility on how your end-users see the path, to improve the delivery performance by removing the need to do TLS negotiations or to improve your brand recognition through URL consistency.</p><p>Still, there was no simple way to tell our product: <i>“Tens of millions of images are in this repository URL. Go and grab them all from me”</i>.  </p><p>In some scenarios, our customers have buckets with millions of images to upload to Cloudflare Images. Their goal is to migrate all objects to Cloudflare through a one-time process, allowing you to drop the external storage altogether.</p><p>In another common scenario, different departments in larger companies use independent systems configured with varying storage repositories, all of which they feed at specific times with uneven upload volumes. And it would be best if they could reuse definitions to get all those new Images in Cloudflare to ensure the portfolio is up-to-date while not paying egregious egress fees by serving the public directly from those multiple storage providers.</p><p>These situations required the upload process to Cloudflare Images to include logistical coordination and scripting knowledge. Until now.</p>
    <div>
      <h3>Announcing the Cloudflare Images Sourcing Kit</h3>
      <a href="#announcing-the-cloudflare-images-sourcing-kit">
        
      </a>
    </div>
    <p>Today, we are happy to share with you our Sourcing Kit, where you can define one or more sources containing the objects you want to migrate to Cloudflare Images.</p><p>But, what exactly is Sourcing? In industries like manufacturing, it implies a number of operations, from selecting suppliers, to vetting raw materials and delivering reports to the process owners.</p><p>So, we borrowed that definition and translated it into a Cloudflare Images set of capabilities allowing you to:</p><ol><li><p>Define one or multiple repositories of images to bulk import;</p></li><li><p>Reuse those sources and import only new images;</p></li><li><p>Make sure that only actual usable images are imported and not other objects or file types that exist in that source;</p></li><li><p>Define the target path and filename for imported images;</p></li><li><p>Obtain Logs for the bulk operations;</p></li></ol><p>The new kit does it all. So let's go through it.</p>
    <div>
      <h3>How the Cloudflare Images Sourcing Kit works</h3>
      <a href="#how-the-cloudflare-images-sourcing-kit-works">
        
      </a>
    </div>
    <p>In the <a href="https://dash.cloudflare.com/?to=/:account/images">Cloudflare Dashboard</a>, you will soon find the Sourcing Kit under Images.</p><p>In it, you will be able to create a new source definition, view existing ones, and view the status of the last operations.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4SZEMoU2nrlZPvDawlEpXZ/f14ac5bdf189995fa2f5c0429811cf6e/image5-12.png" />
            
            </figure><p>Clicking on the create button will launch the wizard that will guide you through the first bulk import from your defined source:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5W4ZRJaDMUAzHpxq3j0Nkc/1401923c512b249b716ddac271ee1ff2/image2-32.png" />
            
            </figure><p>First, you will need to input the Name of the Source and the URL for accessing it. You’ll be able to save the definitions and reuse the source whenever you wish.After running the necessary validations, you’ll be able to define the rules for the import process.</p><p>The first option you have allows an Optional Prefix Path. Defining a prefix allows a unique identifier for the images uploaded from this particular source, differentiating the ones imported from this source.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5syJ0nK1aQtBS1O65s9ao7/1dad4362abe8b7eba03664816090212e/image4-19.png" />
            
            </figure><p>The naming rule in place respects the source image name and path already, so let's assume there's a puppy image to be retrieved at:</p><p><code>[https://my-bucket.s3.us-west-2.amazonaws.com/folderA/puppy.png](https://my-bucket.s3.us-west-2.amazonaws.com/folderA/puppy.png)</code></p><p>When imported without any Path Prefix, you’ll find the image at</p><p><code>[https://imagedelivery.net/&lt;AccountId&gt;/folderA/puppy.png](https://imagedelivery.net/&lt;AccountId&gt;/folderA/puppy.png)</code></p><p>Now, you might want to create an additional Path Prefix to identify the source, for example by mentioning that this bucket is from the Technical Writing department. In the puppy case, the result would be:</p><p><code>[https://imagedelivery.net/&lt;AccountId&gt;/**techwriting**/folderA/puppy.png](https://imagedelivery.net/&lt;AccountId&gt;/techwriting/folderA/puppy.png)</code></p><p>Custom Path prefixes also provide a way to prevent name clashes coming from other sources.</p><p>Still, there will be times when customers don't want to use them. And, when re-using the source to import images, a same path+filename destinations clash might occur.</p><p>By default, we don’t overwrite existing images, but we allow you to select that option and refresh your catalog present in the Cloudflare pipeline.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5xRY7HGX666fWFuaKU8bgm/6f0c97e2c024965bf4129d56c57b224a/image6-12.png" />
            
            </figure><p>Once these inputs are defined, a click on the Create and start migration button at the bottom will trigger the upload process.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/301MOQX804MipTdEAzO3Wz/367288704deb02c6a2d5eb284f214867/image10.png" />
            
            </figure><p>This action will show the final wizard screen, where the migration status is displayed. The progress log will report any errors obtained during the upload and is also available to download.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5qz31zhtbNMiIAiRceslTp/7d2e1128ffd82e86cd4752157fdeaaf9/image7-6.png" />
            
            </figure><p>You can reuse, edit or delete source definitions when no operations are running, and at any point, from the home page of the kit, it's possible to access the status and return to the ongoing or last migration report.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/13JDKst8pmw43G21YQ1yi2/7a5c8ee33a2e3ab31675a4caf97a8e4c/image3-24.png" />
            
            </figure>
    <div>
      <h3>What’s next?</h3>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>With the Beta version of the Cloudflare Images Sourcing Kit, we will allow you to define AWS S3 buckets as a source for the imports. In the following versions, we will enable definitions for other common repositories, such as the ones from Azure Storage Accounts or Google Cloud Storage.</p><p>And while we're aiming for this to be a simple UI, we also plan to make everything available through CLI: from defining the repository URL to starting the upload process and retrieving a final report.</p>
    <div>
      <h3>Apply for the Beta version</h3>
      <a href="#apply-for-the-beta-version">
        
      </a>
    </div>
    <p>We will be releasing the Beta version of this kit in the following weeks, allowing you to source your images from third party repositories to Cloudflare.</p><p>If you want to be the first to use Sourcing Kit, request to join the waitlist on the <a href="https://dash.cloudflare.com/?to=/:account/images">Cloudflare Images dashboard</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/75BuXJQtfqBDv8SOx3JKzs/3b765c95ac58e7bb7a8eeade89d323ea/image1-39.png" />
            
            </figure><p></p> ]]></content:encoded>
            <category><![CDATA[Platform Week]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Cloudflare Images]]></category>
            <category><![CDATA[Developers]]></category>
            <guid isPermaLink="false">72mbRGNXN4aGuGsSccsIqq</guid>
            <dc:creator>Paulo Costa</dc:creator>
            <dc:creator>Natalie Yeh</dc:creator>
            <dc:creator>Yevgen Safronov</dc:creator>
        </item>
        <item>
            <title><![CDATA[Cloudflare Images introduces AVIF, Blur and Bundle with Stream]]></title>
            <link>https://blog.cloudflare.com/images-avif-blur-bundle/</link>
            <pubDate>Thu, 18 Nov 2021 14:00:10 GMT</pubDate>
            <description><![CDATA[ Two months ago we launched Cloudflare Images for everyone and we are amazed about the adoption and the feedback we received. Today we are announcing AVIF and Blur support for Cloudflare Images and give you a preview of the upcoming functionality. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Two months ago we <a href="/announcing-cloudflare-images/">launched</a> Cloudflare Images for everyone, and we are amazed about the adoption and the feedback we received.</p><p>Let’s start with some numbers:</p><p>More than <b>70 million</b> images delivered per day on average in the week of November 5 to 12.</p><p>More than <b>1.5 million</b> images have been uploaded so far, growing faster every day.</p><p>But we are just getting started and are happy to announce the release of the most requested features, first we talk about the AVIF support for Images, converting as many images as possible with <b>AVIF</b> results in highly compressed, fast delivered images without compromising on the quality.</p><p>Secondly we introduce <b>blur</b>. By blurring an image, in combination with the already supported protection of private images via <a href="https://developers.cloudflare.com/images/cloudflare-images/serve-images/serve-private-images-using-signed-url-tokens">signed URL</a>, we make Cloudflare Images a great solution for previews for paid content.</p><p>For many of our customers it is important to be able to serve Images from their <b>own domain</b> and not only via imagedelivery.net. Here we show an easy solution for this using a custom Worker or a special URL.</p><p>Last but not least we announce the launch of new attractively priced <b>bundles</b> for both Cloudflare Images and Stream.</p>
    <div>
      <h3>Images supports AVIF</h3>
      <a href="#images-supports-avif">
        
      </a>
    </div>
    <p>We <a href="/generate-avif-images-with-image-resizing/">announced support</a> for the new AVIF image format in Image Resizing product last year.</p><p>Last month we added AVIF support in Cloudflare Images. It compresses images significantly better than older-generation formats such as WebP and JPEG. Today, AVIF image format is supported both in Chrome and Firefox. <a href="https://caniuse.com/avif">Globally, almost 70%</a> of users have a web browser that supports AVIF.</p>
    <div>
      <h3>What is AVIF</h3>
      <a href="#what-is-avif">
        
      </a>
    </div>
    <p>As we <a href="/generate-avif-images-with-image-resizing/#what-is-avif">explained previously</a>, AVIF is a combination of the HEIF ISO standard, and a royalty-free AV1 codec by <a href="https://aomedia.org/">Mozilla, Xiph, Google, Cisco, and many others</a>.</p><p>“Currently, JPEG is the most popular image format on the web. It's doing remarkably well for its age, and it will likely remain popular for years to come thanks to its excellent compatibility. There have been many previous attempts at replacing JPEG, such as JPEG 2000, JPEG XR, and WebP. However, these formats offered only modest compression improvements and didn't always beat JPEG on image quality. Compression and image quality in <a href="https://netflixtechblog.com/avif-for-next-generation-image-coding-b1d75675fe4">AVIF is better than in all of them, and by a wide margin</a>.”<sup>1</sup></p>
    <div>
      <h3>How Cloudflare Images supports AVIF</h3>
      <a href="#how-cloudflare-images-supports-avif">
        
      </a>
    </div>
    <p>As a reminder, <a href="/building-cloudflare-images-in-rust-and-cloudflare-workers/#image-delivery">image delivery</a> is done through the Cloudflare managed imagedelivery.net domain. It is powered by Cloudflare Workers. We have the following logic to request the AVIF format based on the Accept HTTP request header:</p>
            <pre><code>const WEBP_ACCEPT_HEADER = /image\/webp/i;
const AVIF_ACCEPT_HEADER = /image\/avif/i;

addEventListener("fetch", (event) =&gt; {
  event.respondWith(handleRequest(event));
});

async function handleRequest(event) {
  const request = event.request;
  const url = new URL(request.url);
  
  const headers = new Headers(request.headers);

  const accept = headers.get("accept");

  let format = undefined;

  if (WEBP_ACCEPT_HEADER.test(accept)) {
    format = "webp";
  }

  if (AVIF_ACCEPT_HEADER.test(accept)) {
    format = "avif";
  }

  const resizingReq = new Request(url, {
    headers,
    cf: {
      image: { ..., format },
    },
  });

  return fetch(resizingReq);
}</code></pre>
            <p>Based on the Accept header, the logic in the Worker detects if WebP or AVIF format can be served. The request is passed to Image Resizing. If the image is available in the Cloudflare cache it will be served immediately, otherwise the image will be resized, transformed, and cached. This approach ensures that for clients without AVIF format support we deliver images in WebP or JPEG formats.</p><p>The benefit of Cloudflare Images product is that we added AVIF support without a need for customers to change a single line of code from their side.</p><p>The transformation of an image to AVIF is compute-intensive but leads to a significant benefit in file-size. We are always weighing the cost and benefits in the decision which format to serve.</p><p>It Is worth noting that all the conversions to WebP and AVIF formats happen on the request phase for image delivery at the moment. We will be adding the ability to convert images on the upload phase in the future.</p>
    <div>
      <h3>Introducing Blur</h3>
      <a href="#introducing-blur">
        
      </a>
    </div>
    <p>One of the most requested features for Images and Image Resizing was adding support for blur. We recently added the support for blur both via <a href="https://developers.cloudflare.com/images/image-resizing/url-format">URL format</a> and <a href="https://developers.cloudflare.com/images/image-resizing/resize-with-workers">with Cloudflare Workers</a>.</p><p>Cloudflare Images uses variants. When you create a variant, you can define properties including variant name, width, height, and whether the variant should be publicly accessible. Blur will be available as a new option for variants via <a href="https://api.cloudflare.com/#cloudflare-images-variants-create-a-variant">variant API</a>:</p>
            <pre><code>curl -X POST "https://api.cloudflare.com/client/v4/accounts/9a7806061c88ada191ed06f989cc3dac/images/v1/variants" \
     -H "Authorization: Bearer &lt;api_token&gt;" \
     -H "Content-Type: application/json" \
     --data '{"id":"blur","options":{"metadata":"none","blur":20},"neverRequireSignedURLs":true}'</code></pre>
            <p>One of the use cases for using blur with Cloudflare Images is to control access to the premium content.</p><p>The customer will upload the image that requires an access token:</p>
            <pre><code>curl -X POST "https://api.cloudflare.com/client/v4/accounts/9a7806061c88ada191ed06f989cc3dac/images/v1" \
     -H "Authorization: Bearer &lt;api_token&gt;"
     --form 'file=@./&lt;file_name&gt;' \
     --form 'requireSignedURLs=true'</code></pre>
            <p>Using the variant we defined via API we can fetch the image without providing a signature:</p><img src="https://imagedelivery.net/r1xBEzoDl4p34DP7QLrECw/dfc72df8-863f-46e3-7bba-a21f9795e401/blur20" /><p>To access the protected image a <a href="https://developers.cloudflare.com/images/cloudflare-images/serve-images/serve-private-images-using-signed-url-tokens">valid signed URL</a> will be required:</p><img src="https://imagedelivery.net/r1xBEzoDl4p34DP7QLrECw/dfc72df8-863f-46e3-7bba-a21f9795e401/public?sig=d67d49055d652b8fb2575b3ec11f0e1a8fae3932d3e516d381e49e498dd4a96e" />
Lava lamps in the Cloudflare lobby. Courtesy of <a href="https://twitter.com/mahtin/status/888251632550424577">@mahtin</a>
<br /><p>The combination of image blurring and restricted access to images could be integrated into many scenarios and provides a powerful tool set for content publishers.</p><p>The functionality to define a variant with a blur option is coming soon in the Cloudflare dashboard.</p>
    <div>
      <h3>Serving images from custom domains</h3>
      <a href="#serving-images-from-custom-domains">
        
      </a>
    </div>
    <p>One important use case for Cloudflare Images customers is to serve images from custom domains. It could improve latency and loading performance by not requiring additional TLS negotiations on the client. Using Cloudflare Workers customers can add this functionality today using the following example:</p>
            <pre><code>const IMAGE_DELIVERY_HOST = "https://imagedelivery.net";

addEventListener("fetch", async (event) =&gt; {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const url = new URL(request.url);
  const { pathname, search } = url;

  const destinationURL = IMAGE_DELIVERY_HOST + pathname + search;
  return fetch(new Request(destinationURL));
}</code></pre>
            <p>For simplicity, the Workers script makes the redirect from the domain where it’s deployed to the imagedelivery.net. We assume the same format as for Cloudflare Images URLs:</p>
            <pre><code>https://&lt;customdomain.net&gt;/&lt;encoded account id&gt;/&lt;image id&gt;/&lt;variant name&gt;</code></pre>
            <p>The Worker could be adjusted to fit customer needs like:</p><ul><li><p>Serving images from a specific domains' path e.g. /images/</p></li><li><p>Populate account id or variant name automatically</p></li><li><p>Map Cloudflare Images to custom URLs altogether</p></li></ul><p>For customers who just want the simplicity of serving Cloudflare Images from their domains on Cloudflare we will be adding the ability to serve Cloudflare Images using the following format:</p>
            <pre><code>https://&lt;customdomain.net&gt;/cdn-cgi/imagedelivery/&lt;encrypted_account_id&gt;/&lt;_image_id&gt;/&lt;variant_name&gt;</code></pre>
            <p>Image delivery will be supported from all customer domains under the same Cloudflare account where Cloudflare Images subscription is activated. This will be available to all Cloudflare Images customers before the holidays.</p>
    <div>
      <h3>Images and Stream Bundle</h3>
      <a href="#images-and-stream-bundle">
        
      </a>
    </div>
    <p>Creator platforms, eCommerce, and many other products have one thing in common: having an easy and accessible way to upload, store and deliver your images and videos in the best and most affordable way is vital.</p><p>We teamed up with the Stream team to create a set of bundles that make it super easy to get started with your product.</p><p>The Starter bundle is perfect for experimenting and a first MVP. For just $10 per month it is 50% cheaper than the unbundled option, and includes enough to get started:</p><ul><li><p>Stream: 1,000 stored minutes and 5,000 minutes served</p></li><li><p>Images: 100,000 stored images and 500,000 images served</p></li></ul><p>For larger and fast scaling applications we have the Creator Bundle for $50 per month which saves over 60% compared to the unbundled products. It includes everything to start scaling:</p><ul><li><p>Stream: 10,000 stored minutes and 50,000 minutes served</p></li><li><p>Images: 500,000 stored images and 1,000,000 images served</p></li></ul><img src="https://imagedelivery.net/r1xBEzoDl4p34DP7QLrECw/fb149b8a-8d93-494d-74da-0a88b8ffd600/public" /><p>These new bundles will be available to all customers from the end of November.</p>
    <div>
      <h3>What’s next</h3>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>We are not stopping here, and we already have the next features for Images lined up. One of them is Images Analytics. Having great analytics for a product is vital, and so we will be introducing analytics functionality for Cloudflare Images for all customers to be able to keep track of all images and their usage.</p>
    <div>
      <h3>Watch on Cloudflare TV</h3>
      <a href="#watch-on-cloudflare-tv">
        
      </a>
    </div>
    <div></div>
<p></p>
<div></div><hr /><p><sup>1</sup><a href="/generate-avif-images-with-image-resizing/#what-is-avif">http://blog.cloudflare.com/generate-avif-images-with-image-resizing/#what-is-avif</a></p> ]]></content:encoded>
            <category><![CDATA[Full Stack Week]]></category>
            <category><![CDATA[Image Resizing]]></category>
            <category><![CDATA[Image Optimization]]></category>
            <guid isPermaLink="false">5fZMmfFfa85XpTYFNaU5S2</guid>
            <dc:creator>Marc Lamik</dc:creator>
            <dc:creator>Yevgen Safronov</dc:creator>
        </item>
        <item>
            <title><![CDATA[Building Cloudflare Images in Rust and Cloudflare Workers]]></title>
            <link>https://blog.cloudflare.com/building-cloudflare-images-in-rust-and-cloudflare-workers/</link>
            <pubDate>Wed, 15 Sep 2021 12:59:28 GMT</pubDate>
            <description><![CDATA[ Using Rust and Cloudflare Workers helps us quickly iterate and deliver product improvements over the coming weeks and months. ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/vts7EELfGtvXeEkzKzM3B/0bed2d7c96c8cdb043a810e2c64c96e9/image3-14.png" />
            
            </figure><p>This post explains how we implemented the Cloudflare Images product with reusable Rust libraries and Cloudflare Workers. It covers the technical design of <a href="https://developers.cloudflare.com/image-resizing/">Cloudflare Image Resizing</a> and Cloudflare Images. Using Rust and Cloudflare Workers helps us quickly iterate and deliver product improvements over the coming weeks and months.</p>
    <div>
      <h3>Reuse of code in Rusty image projects</h3>
      <a href="#reuse-of-code-in-rusty-image-projects">
        
      </a>
    </div>
    <p>We developed <a href="https://developers.cloudflare.com/image-resizing/">Image Resizing</a> in Rust. It's a web server that receives HTTP requests for images along with resizing options, fetches the full-size images from the origin, applies resizing and other image processing operations, compresses, and returns the HTTP response with the optimized image.</p><p>Rust makes it easy to split projects into libraries (called crates). The image processing and compression parts of Image Resizing are usable as libraries.</p><p>We also have a product called  <a href="/introducing-polish-automatic-image-optimizati/">Polish</a>, which is a Golang-based service that recompresses images in our cache. Polish was initially designed to run command-line programs like <code>jpegtran</code> and <code>pngcrush</code>. We took the core of Image Resizing and wrapped it in a command-line executable. This way, when Polish needs to apply lossy compression or generate WebP images or animations, it can use Image Resizing via a command-line tool instead of a third-party tool.</p><p>Reusing libraries has allowed us to easily unify processing between Image Resizing and Polish (for example, to ensure that both handle metadata and color profiles in the same way).</p><p>Cloudflare Images is another product we've built in Rust. It added support for a custom storage back-end, variants (size presets), support for signing URLs and more. We made it as a collection of Rust crates, so we can reuse pieces of it in other services running anywhere in our network. Image Resizing provides image processing for Cloudflare Images and shares libraries with Images to understand the new URL scheme, access the storage back-end, and database for variants.</p>
    <div>
      <h3>How Image Resizing works</h3>
      <a href="#how-image-resizing-works">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6LlxMguVop5gSnpNNOszTn/b14d2635eabab9622b6b7dcbada2f408/image-resizing-diagram.png" />
            
            </figure><p>The Image Resizing service runs at the edge and is deployed on every server of the Cloudflare global network. Thanks to Cloudflare's global Anycast network, the closest Cloudflare data center will handle eyeball image resizing requests. Image Resizing is tightly integrated with the Cloudflare cache and handles eyeball requests only on a cache miss.</p><p>There are two ways to use Image Resizing. The default <a href="https://developers.cloudflare.com/image-resizing/url-format">URL scheme</a> provides an easy, declarative way of specifying image dimensions and other options. The other way is to use a JavaScript API in a <a href="https://developers.cloudflare.com/image-resizing/resizing-with-workers">Worker</a>. Cloudflare Workers give powerful programmatic control over every image resizing request.</p>
    <div>
      <h3>How Cloudflare Images work</h3>
      <a href="#how-cloudflare-images-work">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5uCiQpHMXkjdKzBhvIruxh/1c1930777fee1539e201b30eb9bf530a/DES-3375-2.png" />
            
            </figure><p>Cloudflare Images consists of the following components:</p><ul><li><p>The Images core service that powers the public API to manage images assets.</p></li><li><p>The Image Resizing service responsible for image transformations and caching.</p></li><li><p>The Image delivery Cloudflare Worker responsible for serving images and passing corresponding parameters through to the Imaging Resizing service.</p></li><li><p>Image storage that provides access and storage for original image assets.</p></li></ul><p>To support Cloudflare Images scenarios for image transformations, we made several changes to the Image Resizing service:</p><ul><li><p>Added access to Cloudflare storage with original image assets.</p></li><li><p>Added access to variant definitions (size presets).</p></li><li><p>Added support for signing URLs.</p></li></ul>
    <div>
      <h3>Image delivery</h3>
      <a href="#image-delivery">
        
      </a>
    </div>
    <p>The primary use case for Cloudflare Images is to provide a simple and easy-to-use way of managing images assets. To cover egress costs, we provide image delivery through the Cloudflare managed imagedelivery.net domain. It is configured with <a href="/tiered-cache-smart-topology/">Tiered Caching</a> to maximize the cache hit ratio for image assets. imagedelivery.net provides <a href="https://www.cloudflare.com/developer-platform/solutions/hosting/">image hosting</a> without a need to configure a custom domain to proxy through Cloudflare.</p><p>A Cloudflare Worker powers image delivery. It parses image URLs and passes the corresponding parameters to the image resizing service.</p>
    <div>
      <h3>How we store Cloudflare Images</h3>
      <a href="#how-we-store-cloudflare-images">
        
      </a>
    </div>
    <p>There are several places we store information on Cloudflare Images:</p><ul><li><p>image metadata in Cloudflare's core data centers</p></li><li><p>variant definitions in Cloudflare's edge data centers</p></li><li><p>original images in core data centers</p></li><li><p>optimized images in Cloudflare cache, physically close to eyeballs.</p></li></ul><p>Image variant definitions are stored and delivered to the edge using Cloudflare's distributed key-value store called <a href="/introducing-quicksilver-configuration-distribution-at-internet-scale/">Quicksilver</a>. We use a single source of truth for variants. The Images core service makes calls to Quicksilver to read and update variant definitions.</p><p>The rest of the information about the image is stored in the image URL itself:<a href="https://imagedelivery.net/">https://imagedelivery.net/</a>//</p><p> contains a flag, whether it's publicly available or requires access verification. It's not feasible to store any image metadata in Quicksilver as the data volume would increase linearly with the number of images we host. Instead, we only allow a finite number of variants per account, so we responsibly utilize available disk space on the edge. The downside of storing image metadata as part of  is that  will change on access change.</p>
    <div>
      <h3>How we keep Cloudflare Images up to date</h3>
      <a href="#how-we-keep-cloudflare-images-up-to-date">
        
      </a>
    </div>
    <p>The only way to access images is through the use of variants. Each variant is a named <a href="https://developers.cloudflare.com/image-resizing/resizing-with-workers#fetch-options">image resizing configuration</a>. Once the image asset is fetched, we cache the transformed image in the Cloudflare cache. The critical question is how we keep processed images up to date. The answer is by purging the Cloudflare cache when necessary. There are two use cases:</p><ul><li><p>access to the image is changed</p></li><li><p>the variant definition is updated</p></li></ul><p>In the first instance, we purge the cache by calling a URL:<a href="https://imagedelivery.net/"><i>https://imagedelivery.net/</i></a><i>/</i></p><p>Then, the customer updates the variant we issue a cache purge request by tag:<i>account-id/variant-name</i></p><p>To support cache purge by tag, the image resizing service adds the necessary tags for all transformed images.</p>
    <div>
      <h3>How we restrict access to Cloudflare Images</h3>
      <a href="#how-we-restrict-access-to-cloudflare-images">
        
      </a>
    </div>
    <p>The Image resizing service supports restricted access to images by using URL signatures with expiration. URLs are signed with an SHA-256 HMAC key. The steps to produce valid signatures are:</p><ol><li><p>Take the path and query string (the path starts with /).</p></li><li><p>Compute the path’s SHA-256 HMAC with the query string, using the Images' URL signing key as the secret. The key is configured in the Dashboard.</p></li><li><p>If the URL is meant to expire, compute the Unix timestamp (number of seconds since 1970) of the expiration time, and append <code>?exp=</code> and the timestamp as an integer to the URL.</p></li><li><p>Append <code>?</code> or <code>&amp;</code> to the URL as appropriate (<code>?</code> if it had no query string; <code>&amp;</code> if it had a query string).</p></li><li><p>Append <code>sig=</code> and the HMAC as hex-encoded 64 characters.</p></li></ol><p>A signed URL looks like this:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5M87aEfnYeWRuAJk9MJaI0/c8d289f755d39a2885fc247358f9a32a/signed-1.png" />
            
            </figure><p>A signed URL with an expiration timestamp looks like this:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6cVOACVdzpkmfB6BHXsxk/23c586690ee6d72a0460f866b8f7bcdf/signed-2.png" />
            
            </figure><p>Signature of /hello/world URL with a secret ‘this is a secret’ is <code>6293f9144b4e9adc83416d1b059abcac750bf05b2c5c99ea72fd47cc9c2ace34</code>.</p><p><code>https://imagedelivery.net/hello/world?sig=6293f9144b4e9adc83416d1b059abcac750bf05b2c5c99ea72fd47cc9c2ace34</code></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5SZF6WjgpV8GjeqEFbT9Yk/516d7148a25154db914bd6f41f685a3b/JS.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7nkNNkzLLNwZAYLSzt6NPI/3696a549ae7cee402d4d335419b88c19/Rust.png" />
            
            </figure>
    <div>
      <h3>Direct creator uploads with Cloudflare Worker and KV</h3>
      <a href="#direct-creator-uploads-with-cloudflare-worker-and-kv">
        
      </a>
    </div>
    <p>Similar to <a href="https://developers.cloudflare.com/stream/uploading-videos/direct-creator-uploads">Cloudflare Stream</a>, Images supports direct creator uploads. That allow users to upload images without API tokens. Everyday use of direct creator uploads is by web apps, client-side applications, or mobile apps where users upload content directly to Cloudflare Images.</p><p>Once again, we used our serverless platform to support direct creator uploads. The successful API call stores the account's information in Workers KV with the specified expiration date. A simple Cloudflare Worker handles the upload URL, which reads the KV value and grants upload access only on a successful call to KV.</p>
    <div>
      <h3>Future Work</h3>
      <a href="#future-work">
        
      </a>
    </div>
    <p>Cloudflare Images product has an exciting product roadmap. Let’s review what’s possible with the current architecture of Cloudflare Images.</p>
    <div>
      <h4>Resizing hints on upload</h4>
      <a href="#resizing-hints-on-upload">
        
      </a>
    </div>
    <p>At the moment, no image transformations happen on upload. That means we can serve the image globally once it is uploaded to Image storage. We are considering adding resizing hints on image upload. That won't necessarily schedule image processing in all cases but could provide a valuable signal to resize the most critical image variants. An example could be to <a href="/generate-avif-images-with-image-resizing/">generate an AVIF</a> variant for the most vital image assets.</p>
    <div>
      <h4>Serving images from custom domains</h4>
      <a href="#serving-images-from-custom-domains">
        
      </a>
    </div>
    <p>We think serving images from a domain we manage (with Tiered Caching) is a great default option for many customers. The downside is that loading Cloudflare images requires additional TLS negotiations on the client-side, adding latency and impacting loading performance. On the other hand, serving Cloudflare Images from custom domains will be a viable option for customers who set up a website through Cloudflare. The good news is that we can support such functionality with the current architecture without radical changes in the architecture.</p>
    <div>
      <h3>Conclusion</h3>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>The Cloudflare Images product runs on top of the Cloudflare global network. We built Cloudflare Images in Rust and Cloudflare Workers. This way, we use Rust reusable libraries in several products such as Cloudflare Images, Image Resizing, and Polish. Cloudflare’s serverless platform is an indispensable tool to build Cloudflare products internally. If you are interested in building innovative products in Rust and Cloudflare Workers, <a href="https://www.cloudflare.com/careers/jobs/?department=Emerging%20Technology%20and%20Incubation&amp;location=default">we're hiring</a>.</p>
    <div>
      <h3>Watch on Cloudflare TV</h3>
      <a href="#watch-on-cloudflare-tv">
        
      </a>
    </div>
    <div></div><p></p> ]]></content:encoded>
            <category><![CDATA[Speed Week]]></category>
            <category><![CDATA[Cloudflare Images]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Rust]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">21W0eo2QBR2GXgCMaVjCuY</guid>
            <dc:creator>Yevgen Safronov</dc:creator>
        </item>
        <item>
            <title><![CDATA[Automatic Platform Optimization post-launch report]]></title>
            <link>https://blog.cloudflare.com/apo-post-launch-report/</link>
            <pubDate>Tue, 16 Mar 2021 12:00:00 GMT</pubDate>
            <description><![CDATA[ We explored almost 200 websites with the activated Automatic Platform Optimization feature in Chrome User Experience Report data. Automatic Platform Optimization consistently demonstrated better aggregate performance among sites we analyzed in TTFB, First Paint, FCP, and LCP metrics. ]]></description>
            <content:encoded><![CDATA[ <p>Last year during <a href="/automatic-platform-optimizations-starting-with-wordpress/">Birthday Week</a>, we announced Automatic Platform Optimization for WordPress (APO): smart HTML caching for WordPress sites using Cloudflare. Initial testing across various WordPress sites demonstrated significant improvements in performance metrics like Time to First Byte (TTFB), First Contentful Paint (FCP), and Speed Index. We wanted to measure how APO impacted web performance for our customers since the launch.</p><p>In the blog post, we answer the following questions:</p><ul><li><p>How fast is Automatic Platform Optimization? Can you demonstrate it with data?</p></li></ul><p>We will show real-world improvements for several performance metrics.</p><ul><li><p>Is Automatic Platform Optimization flexible enough to integrate smoothly with my WordPress site?</p></li></ul><p>We have added and improved lots of features since the initial launch.</p><ul><li><p>Will Automatic Platform Optimization work when used with other plugins?</p></li></ul><p>We will cover the most common use cases and explain how Automatic Platform Optimization could be fined-tuned.</p>
    <div>
      <h2>Measuring performance with WebPageTest</h2>
      <a href="#measuring-performance-with-webpagetest">
        
      </a>
    </div>
    <p>We use WebPageTest as a go-to tool for <a href="/new-speed-page/">synthetic testing at Cloudflare</a>. It measures web performance metrics in real browsers, is highly programmable, and could scale to test millions of sites per day. Among the benefits of synthetic testing are easy to produce results and their relatively high reproducibility.</p><p>Automatic Platform Optimization internal testing with WebPageTest demonstrated a very promising 72% reduction in Time to First Byte (TTFB) and 23% reduction to <a href="https://web.dev/fcp/">First Contentful Paint</a>. Follow <a href="/automatic-platform-optimizations-starting-with-wordpress/#the-benefits-of-automatic-platform-optimization">the original blog post</a> to learn more about our test setup and results analysis.</p>
    <div>
      <h2>Measuring performance with the Chrome User Experience Report</h2>
      <a href="#measuring-performance-with-the-chrome-user-experience-report">
        
      </a>
    </div>
    <p>In comparison to synthetic testing, Real User Monitoring (RUM) is invaluable in painting the picture of how a website performs in real-world conditions: on different form factors and variable connection types. Although noise to signal ratio could be high in RUM data, there is no substitute for measuring web performance in the wild.</p><p>We analyzed Google's <a href="https://developers.google.com/web/tools/chrome-user-experience-report">Chrome User Experience Report</a> of Automatic Platform Optimization websites and compared results from two months before enabling CrUX to the two months after. We present results of Time To First Byte, First Paint, First Contentful Paint, and Largest Contentful Paint.</p>
    <div>
      <h3>Time To First Byte by device</h3>
      <a href="#time-to-first-byte-by-device">
        
      </a>
    </div>
    <p>Time To First Byte (TTFB) is the time taken from the user or client making an HTTP request until the first byte arrives back to the browser.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7ukvHUfnTwdV6G9O2tInJK/da54e8a03550745d5c304c3664cacdb0/image1-15.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/nDmdI6LfhEhCmwrw8YAdE/6f4d30993e12970e71a0ad6efb0499b6/image6-5.png" />
            
            </figure><p>Automatic Platform Optimization improvements in the TTFB metric demonstrated the largest increase in the 'good' bucket and the largest decrease in the 'poor' bucket both on desktop and phone form factors among all metrics. The improvement in the 'poor' bucket on mobile demonstrates how Automatic Platform Optimization makes a difference even on slow connection types like 3G, 2G, slow 2G. Faster response times with Automatic Platform Optimization from edge servers translate directly into quicker TTFB timings, positively affecting all other performance measurements.</p>
    <div>
      <h3>First Paint by device</h3>
      <a href="#first-paint-by-device">
        
      </a>
    </div>
    <p>First Paint measures the first time the browser rendered any content. First Paint signifies the earliest point when something happens on the screen after a user requests a page. It is a good proxy for a user believing the website is not broken.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5crFujbvMLpxg5SWITmSm5/c8159070f9395e2b2a22b44bd860efad/image3-12.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6aaGYS8TwMdLUbyI75g7jA/69edacd19a63b2f3bbc013e056537fb8/image7-4.png" />
            
            </figure><p>Almost a 10% increase in the 'good' bucket on a desktop is the highlight for the First Paint metric. It's nice to see a clear trend of improvement in both desktop and phone data. It's also worth mentioning that the exact values used to define 'good’, 'moderate’, and the 'poor' buckets are picked arbitrarily for each timing metric. This trend means it's more important to look at the percentage of improvement rather than absolute values for each 'bucket'.</p>
    <div>
      <h3>First Contentful Paint by device</h3>
      <a href="#first-contentful-paint-by-device">
        
      </a>
    </div>
    <p>First Contentful Paint (FCP) metric measures how long a page takes to start rendering any text, image, non-white canvas, or SVG content. FCP is a good indicator of perceived speed as it portrays how long people wait to see the first signs of a site loading.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4DmAXL8e0YkFDplrWevrkS/f4f720ab35931d8b6afd5f7ff034f54d/image8-4.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6SPbEFcNeYbmiKYAkGnLZk/1b9f60e6c36257cb45ff879498aaa0ee/image5-12.png" />
            
            </figure><p>It is the third straight metric that has been improved in both form factors after customers activated Automatic Platform Optimization. FCP happens even later than the First Paint event. We can draw a hypothesis that Automatic Platform Optimization positively impacts the loading performance metrics of the site. Still, the later the event happens, the less impact Automatic Platform Optimization has on that particular metric.</p>
    <div>
      <h3>Largest Contentful Paint by device</h3>
      <a href="#largest-contentful-paint-by-device">
        
      </a>
    </div>
    <p>The Largest Contentful Paint (LCP) metric reports the render time for the largest image or text block visible within the viewport.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5VPFYu1C2Z4QJGbIUVuOTn/c41163eabc93b087c93a6da4a2f2a329/image4-11.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1UvAakum79HmgCxkdj9Hw3/4223477af4ccc5ca6dbf702ce2d3da5d/image2-10.png" />
            
            </figure><p>Our hypothesis holds ground with LCP as well. Interestingly enough, the positive impact of Automatic Platform Optimization activation is relatively equal on desktops and phones.</p>
    <div>
      <h3>Summary</h3>
      <a href="#summary">
        
      </a>
    </div>
    <p>Overall, Automatic Platform Optimization consistently demonstrated better aggregate performance among sites we analyzed in TTFB, First Paint, FCP, and LCP metrics. Even more impressive are improvements on both desktop and phone form factors. It’s worth pointing out that apart from noticeable differences in hardware characteristics, phone data capture all mobile connection types from slow 2G to fast 4G.</p><p>We explored almost 200 websites with the activated Automatic Platform Optimization feature in Chrome User Experience Report data. To smooth the variance, we combined two months of data before and after Automatic Platform Optimization activation. To further decrease inaccuracy, we dropped a month’s worth of data that included the activation period. As an example, for a website that activated Automatic Platform Optimization last October, we used Chrome User Experience Report measurements from August and September as the <code>before</code> bucket. The <code>after</code> bucket combined data from November and December.</p><p>It is important to note that due to the limitations of iOS, Chrome User Experience Report mobile results don't include devices running Apple's mobile operating system.</p><p>Chrome User Experience Report data provides performance metrics per geographic location, form factor, or connection type. We analyzed aggregated data across all countries and connection types to focus on the overall performance.</p>
    <div>
      <h2>Extended Automatic Platform Optimization Functionality</h2>
      <a href="#extended-automatic-platform-optimization-functionality">
        
      </a>
    </div>
    <p>Since the product launch, we have been listening carefully to the customers' reports of Automatic Platform Optimization’s missing functionality or unexpected behavior. The number of different use cases our customers have underlines how broad the WordPress ecosystem is. One of the advantages of Automatic Platform Optimization utilizing the Workers platform is that we can quickly iterate and release in a matter of hours instead of days or weeks. Granted, some features like Cache By Device Type or subdomains support took us longer to build. Still, for apparent bugs or missing functionality, the ability to release on demand made all the difference for the team and our customers.</p><p>We start the second part of the report with a brief description of the features we have released since October. Afterward, we will paint a bigger picture of how Automatic Platform Optimization fits together with a broad WordPress plugins ecosystem.</p>
    <div>
      <h3>Smart caching for marketing query parameters</h3>
      <a href="#smart-caching-for-marketing-query-parameters">
        
      </a>
    </div>
    <p>By default Automatic Platform Optimization doesn’t cache pages with query parameters. One of the first feature requests from the community was to add caching support for marketing attribution (for example, UTMs) query parameters. We did exactly that, and the full list of the supported parameters is in <a href="https://support.cloudflare.com/hc/en-us/articles/360049822312#h_01EN42YA87TDRX47MSB2XC61Q9">the documentation</a>.</p>
    <div>
      <h3>Improved cache hit ratio</h3>
      <a href="#improved-cache-hit-ratio">
        
      </a>
    </div>
    <p>Cloudflare provides <a href="https://support.cloudflare.com/hc/en-us/articles/200172516">static caching</a> out of the box by default. The caching system for static content relies on file extensions to determine the content type. In contrast, HTML pages don't always have file extensions in the URL. That's why Automatic Platform Optimization caching relies on HTTP's <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation">content negotiation mechanism</a> for HTML content.</p><p>We check both the request's Accept and the response's Content-Type headers for the 'text/html' substring. When humans access sites on the Internet, browsers send correct Accept headers behind the scene. When bots access the sites, they don't always send Accept headers. Initially, the Automatic Platform Optimization cache passed all requests without a valid Accept header to the origin servers. When customers tried to migrate from using the "Cache Everything" page rule to only using Automatic Platform Optimization, they noticed extra load on the origin servers.. Now all GET and HEAD requests are checked against the Automatic Platform Optimization cache. The change noticeably improved the cache hit ratio for all Automatic Platform Optimization customers and enhanced page loads for good bots.</p>
    <div>
      <h3>Improved security</h3>
      <a href="#improved-security">
        
      </a>
    </div>
    <p><a href="https://portswigger.net/research/practical-web-cache-poisoning">Cache poisoning</a> is a common attack vector against any caching system. One of the benefits of Automatic Platform Optimization is that most of the logic runs on edge servers, and we can update it without any changes on the origin server. To mitigate potential cache poisoning, we released a new version to bypass caching if any of the following request headers are present:</p><ul><li><p>X-Host</p></li><li><p>X-Forwarded-Host</p></li><li><p>X-Original-URL</p></li><li><p>X-Rewrite-URL</p></li></ul><p>Additionally, any GET request <a href="https://portswigger.net/research/web-cache-entanglement#fatget">with a body</a> will bypass Automatic Platform Optimization caching.</p>
    <div>
      <h3>Page Rules integration</h3>
      <a href="#page-rules-integration">
        
      </a>
    </div>
    <p>Automatic Platform Optimization's primary goal is to improve page load performance while keeping the configuration as simple as possible. On the other hand, the Automatic Platform Optimization service should allow fine-tuning for advanced use cases. One such mechanism is Cloudflare's <a href="https://support.cloudflare.com/hc/en-us/articles/218411427-Understanding-and-Configuring-Cloudflare-Page-Rules-Page-Rules-Tutorial-">Page Rules</a>. As of today, Automatic Platform Optimization supports the following rules:</p><ul><li><p>Cache Level: Bypass</p></li><li><p>Cache Level: Ignore Query String</p></li><li><p>Cache Level: Cache Everything</p></li><li><p>Bypass Cache on Cookie (Biz and Ent plans only)</p></li><li><p>Edge Cache TTL</p></li><li><p>Browser Cache TTL</p></li></ul><p>For a detailed description, refer to the <a href="https://support.cloudflare.com/hc/en-us/articles/360049822312-Understanding-Automatic-Platform-Optimization-APO-with-WordPress#h_01ER098Z0DRS95ERCMZRV3022S">official documentation</a>.</p>
    <div>
      <h3>Subdomain Support</h3>
      <a href="#subdomain-support">
        
      </a>
    </div>
    <p>Automatic Platform Optimization aims to provide seamless integration with the WordPress ecosystem. It recognizes specific cookies for the most popular plugins like WooCommerce, JetPack, BigCommerce, Easy Digital Downloads, etc.</p><p>Currently, we limit Automatic Platform Optimization usage to WordPress sites. During the initial launch, we restricted Automatic Platform Optimization to run against root domains only, but we learned later of the high demand to run Automatic Platform Optimization on subdomains. To make it possible, we updated both the plugin and <a href="https://api.cloudflare.com/#zone-settings-change-automatic-platform-optimization-for-wordpress-setting">API</a> to allow Automatic Platform Optimization to run on subdomains. Three steps are required to enable Automatic Platform Optimization on a subdomain:</p><ul><li><p>Install version 3.8.7 or later of the Cloudflare WordPress plugin.</p></li><li><p>Log in using Global key. (You can only use an API token for the root domain.)</p></li><li><p>Enable APO. The subdomain displays in the list of hostnames in the card.</p></li></ul><p>The initial cost of $5 per month for Free plans includes running Automatic Platform Optimization against any number of subdomains.</p>
    <div>
      <h3>Caching by Device Type</h3>
      <a href="#caching-by-device-type">
        
      </a>
    </div>
    <p>The majority of site visits come from users accessing the web on <a href="https://www.perficient.com/insights/research-hub/mobile-vs-desktop-usage-study">mobile devices</a>. And website visitors expect sites to work well on mobile devices. In fact, responsive design is a recommended approach for websites. APO works well for responsive design websites because the cache's content will adjust to the client's screen size seamlessly. The alternative method is to serve different markup on mobile browsers.</p><p>Many popular WordPress plugins add a mobile-friendly theme to the site. For sites with such plugins installed, Automatic Platform Optimization breaks functionality by serving the same cached version for all users. As we learned about the growing number of customers with this problem, we looked for the solution. Cloudflare’s caching already has support for <a href="https://support.cloudflare.com/hc/en-us/articles/229373388-Understand-Cache-by-Device-Type-Enterprise-plans-only-">cache by device type</a> functionality, but it's only available for customers on the Enterprise plan. As was a case for the <a href="https://support.cloudflare.com/hc/en-us/articles/236166048">Bypass Cache on Cookie</a> page rule, we decided to include the functionality as part of the Automatic Platform Optimization offering. As a recap, Caching by Device Type relies on the User-Agent request header for detection. There are three types:</p><ul><li><p>Mobile</p></li><li><p>Tablet</p></li><li><p>Everything else</p></li></ul><p>For each type, the Cloudflare cache will store content in a separate bucket. To enable caching by device type, either navigate to the dashboard's Automatic Platform Optimization card or the Cloudflare WordPress plugin. We recommend using a single cache whenever possible because caching by device type can decrease cache hit ratio and increase the load on origin servers.</p>
    <div>
      <h3>Other noticeable changes</h3>
      <a href="#other-noticeable-changes">
        
      </a>
    </div>
    <p>There are many improvements including:</p><ul><li><p>Improved purging infrastructure of content from KV.</p></li><li><p>Extended automatic cache purging for categories and tags.</p></li><li><p>Addressed many edge cases for Google Fonts optimization.</p></li><li><p>Added support for HEAD requests.</p></li><li><p>Automated release pipeline for the Cloudflare WordPress plugin.</p></li></ul>
    <div>
      <h2>Improved WordPress plugins compatibility</h2>
      <a href="#improved-wordpress-plugins-compatibility">
        
      </a>
    </div>
    <p>There are over 50,000 WordPress plugins currently available for download, and because there are so many, we can't test the compatibility of Automatic Platform Optimization with each one individually. We do know, however, that it's vital to provide compatibility for the most popular plugins. Thanks to the vibrant community, we quickly learned about the most widely used issues with Automatic Platform Optimization caching. The plugins that experienced problems could be grouped into the following categories:</p><ul><li><p>Plugins with custom cookies</p></li><li><p>Plugins with geolocation functionality</p></li><li><p>Plugins with mobile themes</p></li><li><p>Plugins with AMP support</p></li><li><p>Plugins that generate HTML</p></li><li><p>Caching and optimizations plugins</p></li></ul><p>Let's review those categories and available solutions for improved compatibility.</p>
    <div>
      <h3>Plugins with custom cookies</h3>
      <a href="#plugins-with-custom-cookies">
        
      </a>
    </div>
    <p>One of the critical features Automatic Platform Optimization provides out of the box is cookie-based rules to bypass APO caching. For any plugin that uses custom cookies, Automatic Platform Optimization requires extending the rules. We have a list of <a href="https://support.cloudflare.com/hc/en-us/articles/360049822312#h_01EQ44V6KRFM6Z0F06MM0EJGJ5">supported plugins</a> that uses our cookies bypass logic.</p>
    <div>
      <h3>Plugins with geolocation functionality</h3>
      <a href="#plugins-with-geolocation-functionality">
        
      </a>
    </div>
    <p>This broad category of plugins relies on geolocation information based on the client's (visitor) IP address (connecting to Cloudflare) to provide its functionality. Early on, we had a misconfiguration in Automatic Platform Optimization functionality that resulted in sending a dummy IP address in the CF-Connecting-IP request header that was forwarded to the origin server.</p><p>This behavior effectively broke the functionality of the widely used Wordfence Security plugin. We promptly released a fix. Because we use Cloudflare Workers internally, Cloudflare Workers treated Automatic Platform Optimization requests sent to the origin as cross-zone requests due to security concerns. As a result, <a href="https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-">the CF-Connecting-IP</a> header value was replaced with a dummy IP address. The change was to send the Automatic Platform Optimization worker's subrequest to the origin as a same-zone request to pass the real client IP without a security concern. Also, Automatic Platform Optimization now sends the client's IP via the X-Forwarded-For request header to the origin to improve plugin compatibility.</p>
    <div>
      <h3>Plugins with mobile themes</h3>
      <a href="#plugins-with-mobile-themes">
        
      </a>
    </div>
    <p>There are several WordPress plugins that render custom themes for mobile visitors. Those plugins rely on the browser's User-Agent to detect visitors from mobile devices. In December, we released Automatic Platform Optimization support for the "Cache by device type" feature. With a single configuration change, you can activate a separate cache based on the device type: mobile, tablet, and everything else. You can learn more about the feature in the <a href="https://support.cloudflare.com/hc/en-us/articles/360049822312#h_01ERZ6QHBGFVPSC44SJAC1YM6Q">official documentation</a>.</p>
    <div>
      <h3>Plugins with AMP support</h3>
      <a href="#plugins-with-amp-support">
        
      </a>
    </div>
    <p>The AMP (<a href="https://amp.dev/">Accelerated Mobile Pages</a>) project's goal is to make the web, and, in particular, the mobile web, much more pleasant to surf. The AMP HTML framework is designed to help web pages load quickly and avoid distracting the user with irrelevant content.</p><p>The most popular AMP WordPress plugins render AMP-compatible markup when the page URL contains the amp= query parameter. AMP markup is a subset of HTML with several restrictions and we looked into possible solutions to provide Automatic Platform Optimization caching for AMP pages. It requires a separate cache for AMP pages similar to the "Cache by device type" feature. Considering Google's recent push with Core Web Vitals, the AMP format's importance will decrease going forward. Based on the complexity of supporting dedicated AMP caching and Google's deprioritization of the AMP format, we decided to bypass Automatic Platform Optimization caching of AMP pages.</p><p>There are two possible approaches for caching AMP pages. The first one is to change the URL schema for AMP pages from, for example, site.com/page/?amp= to site.com/amp/page/. With this option, Automatic Platform Optimization caches AMP pages out of the box. Another solution is to activate the "Cache Everything" Page rule for AMP pages served with amp= query parameter. Note that AMP pages will require manual cache purging in both cases on content change.</p>
    <div>
      <h3>Plugins that generate HTML</h3>
      <a href="#plugins-that-generate-html">
        
      </a>
    </div>
    <p>Using Automatic Platform Optimization with Page Rules makes it possible to:</p><ul><li><p>Bypass caching pages that contain CAPTCHAs.</p></li><li><p>Set Edge TTL for pages that contain nonces or server-rendered ads to six hours or shorter.</p></li></ul>
    <div>
      <h3>Caching and optimizations plugins</h3>
      <a href="#caching-and-optimizations-plugins">
        
      </a>
    </div>
    <p>Among the most popular caching and optimizations WordPress plugins are LiteSpeed Cache, W3 Total Cache, WP Rocket, WP Fastest Cache, WP Super Cache, Autoptimize. To successfully activate Advanced Platform Optimization when any of the plugins above already present, follow these steps:</p><ul><li><p><a href="https://support.cloudflare.com/hc/en-us/articles/360049822312#h_01EKKWPM2KC6H9CD0ATX98KG1C">Install</a> and activate the Cloudflare WordPress plugin.</p></li><li><p>Enable Automatic Platform Optimization in the plugin.</p></li><li><p>Clear any server cache used via other plugins.</p></li><li><p>Verify that your origin starts serving the response header "cf-edge-cache: cache,platform=wordpress".</p></li></ul><p>That should make caching plugins and Automatic Platform Optimization compatible.</p><p>In using optimizations features inside the plugins, additional steps are necessary to integrate with Automatic Platform Optimization. Any of the following plugin's optimizations require subsequent purging of Cloudflare cache:</p><ul><li><p>JavaScript minification and async-loading</p></li><li><p>CSS minification, inlining, and aggregation</p></li><li><p>HTML minification</p></li><li><p>Images optimization and lazy-loading</p></li><li><p>Google fonts optimizations</p></li></ul><p>There are three potential solutions we discuss in the order of preference.</p>
    <div>
      <h4>1. Use Cloudflare products</h4>
      <a href="#1-use-cloudflare-products">
        
      </a>
    </div>
    <p>Most of the optimizations are possible with Cloudflare today:</p><ul><li><p>Auto Minify provides minification for HTML, CSS, and JavaScript</p></li><li><p>Rocket Loader provides JavaScript lazy loading</p></li><li><p>Mirage and Image-resizing allows image optimization and lazy-loading</p></li><li><p>Advanced Platform Optimization optimizes Google fonts out of the box</p></li></ul>
    <div>
      <h4>2. Activate plugins integration with Cloudflare</h4>
      <a href="#2-activate-plugins-integration-with-cloudflare">
        
      </a>
    </div>
    <ul><li><p>WP Rocket <a href="https://docs.wp-rocket.me/article/18-using-wp-rocket-with-cloudflare">integrates</a> with Cloudflare API.</p></li><li><p>WP Fastest Cache <a href="https://www.wpfastestcache.com/tutorial/wp-fastest-cache-cloudflare/">integrates</a> with Cloudflare API.</p></li><li><p>W3 Total Cache <a href="https://kinsta.com/blog/w3-total-cache/#how-to-set-up-w3-total-cache-with-the-cloudflare-extension">integrates</a> with Cloudflare API. Make sure to enable the Page Caching option.</p></li></ul>
    <div>
      <h4>3. Integration with Cloudflare cache purging</h4>
      <a href="#3-integration-with-cloudflare-cache-purging">
        
      </a>
    </div>
    <p>The rest of the plugins in the list, when producing content optimizations, require triggering of Cloudflare cache purging manually or via <a href="https://api.cloudflare.com/#zone-purge-files-by-url">API</a>:</p><ul><li><p>LiteSpeed Cache</p></li><li><p>WP Super Cache</p></li><li><p>Autoptimize</p></li></ul>
    <div>
      <h3>Summary</h3>
      <a href="#summary">
        
      </a>
    </div>
    <p>Automatic Platform Optimization is compatible with the most popular caching plugins. Content optimizations should be preferably migrated to Cloudflare offerings. Alternatively, plugins require triggering Cloudflare cache purging via API integration. The action of the last resort is to disable plugins optimizations but keep caching functionality.</p><p>We work closely with the WordPress plugins community to improve compatibility with Advanced Platform Optimization.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>Automatic Platform Optimization demonstrated improved performance metrics in both synthetic and real-world settings. The public Chrome User Experience Report dataset proved to be an invaluable source of RUM metrics for web performance analysis. Automatic Platform Optimization showed noticeable improvements on desktops and phones. TTFB is the most improved metric, but we also noticed positive changes in the First Paint, First Contentful Paint, and Largest Contentful Paint metrics.</p><p>It has been intensive and rewarding several months since the Automatic Platform Optimization launch, and we greatly increased Automatic Platform Optimization applicability based on customer feedback. Our <a href="https://community.cloudflare.com/">community forum</a> is a great place to get help and ask questions about Cloudflare products, including Advanced Platform Optimization.</p><p>There are more exciting Automatic Platform Optimization improvements the team is actively working on, and we can't wait to share them. Stay tuned!</p> ]]></content:encoded>
            <category><![CDATA[Automatic Platform Optimization]]></category>
            <category><![CDATA[WordPress]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Cache]]></category>
            <category><![CDATA[Performance]]></category>
            <guid isPermaLink="false">3f2zJqUMBglhwX15U1lOPm</guid>
            <dc:creator>Yevgen Safronov</dc:creator>
        </item>
        <item>
            <title><![CDATA[Building Automatic Platform Optimization for WordPress using Cloudflare Workers]]></title>
            <link>https://blog.cloudflare.com/building-automatic-platform-optimization-for-wordpress-using-cloudflare-workers/</link>
            <pubDate>Fri, 02 Oct 2020 13:00:00 GMT</pubDate>
            <description><![CDATA[ zero-config edge caching and optimizations for improved performance for WordPress. Reduce WordPress plugin burden. ]]></description>
            <content:encoded><![CDATA[ <p>This post explains how we implemented the <a href="/automatic-platform-optimizations-starting-with-wordpress/">Automatic Platform Optimization</a> for WordPress. In doing so, we have defined a new place to run WordPress plugins, at the edge written with Cloudflare Workers. We provide the feature as a Cloudflare service but what’s exciting is that anyone could build this using the Workers platform.</p><p>The service is an evolution of the ideas explained in an earlier <a href="/improving-html-time-to-first-byte/">zero-config edge caching of HTML</a> blog post. The post will explain how Automatic Platform Optimization combines the best qualities of the regular Cloudflare cache with Workers KV to improve cache cold starts globally.</p><p>The optimization will work both with and without the <a href="https://support.cloudflare.com/hc/en-us/articles/227634427-Using-Cloudflare-with-WordPress">Cloudflare for WordPress plugin</a> integration. Not only have we provided a zero config edge HTML caching solution but by using the Workers platform we were also able to improve the performance of <a href="/fast-google-fonts-with-cloudflare-workers/">Google font loading</a> for all pages.</p><p>We are launching the feature first for WordPress specifically but the concept can be applied to any website and/or content management system (CMS).</p>
    <div>
      <h3>A new place to run WordPress plugins?</h3>
      <a href="#a-new-place-to-run-wordpress-plugins">
        
      </a>
    </div>
    <p>There are many individual WordPress plugins for performance that use similar optimizations to existing Cloudflare services. Automatic Platform Optimization is bringing them all together into one easy to use solution, deployed at the edge.</p><p>Traditionally you have to maintain server plugins with your WordPress installation. This comes with maintenance costs and can require a deep understanding of how to fine tune performance and security for each and every plugin. Providing the optimizations on the client side can also lead to performance problems due to the <a href="https://v8.dev/blog/cost-of-javascript-2019">costs of JavaScript</a> execution. In contrast most of the optimizations could be built-in in Cloudflare’s edge rather than running on the server or the client. Automatic Platform Optimization will be always up to date with the latest performance and <a href="https://www.cloudflare.com/learning/security/how-to-improve-wordpress-security/">security best practices</a>.</p>
    <div>
      <h3>How to optimize for WordPress</h3>
      <a href="#how-to-optimize-for-wordpress">
        
      </a>
    </div>
    <p>By default Cloudflare CDN caches assets based on <a href="https://support.cloudflare.com/hc/en-us/articles/200172516#h_a01982d4-d5b6-4744-bb9b-a71da62c160a">file extension</a> and doesn’t cache HTML content. It is possible to configure HTML caching with a <a href="https://support.cloudflare.com/hc/en-us/articles/202775670#3SVKvGhbS9BNT34zRCsPJ7">Cache Everything Page</a> rule but it is a manual process and often requires additional features only available on the Business and Enterprise plans. So for the majority of the WordPress websites even with a <a href="https://www.cloudflare.com/learning/cdn/what-is-a-cdn/">CDN</a> in front them, HTML content is not cached. Requests for a HTML document have to go all the way to the origin.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/14SelFUXAc9SBPwqea8OUD/92f9e753055a0759bc0f87ef7c021bca/image3-2.png" />
            
            </figure><p>Even if a CDN optimizes the connection between the closest edge and the website’s origin, the origin could be located far away and also be <a href="https://www.cloudflare.com/learning/cdn/common-cdn-issues/">slow to respond</a>, especially under load.</p>
    <div>
      <h3>Move content closer to the user</h3>
      <a href="#move-content-closer-to-the-user">
        
      </a>
    </div>
    <p>One of the primary recommendations for speeding up websites is to move content closer to the end-user. This reduces the amount of time it takes for packets to travel between the end-user and the web server - the <a href="https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/">round-trip time (RTT)</a>. This improves the speed of establishing a connection as well as serving content from a closer location.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/VJdt6X0xF6xuDyMI0CHXu/4e17456b4b864eb1b5eabc08713a4438/image6-1.png" />
            
            </figure><p>We have previously blogged about the <a href="/improving-html-time-to-first-byte/">benefits of edge caching HTML</a>. Caching and serving from HTML from the Cloudflare edge will greatly improve the time to first byte (TTFB) by optimizing DNS, connection setup, SSL negotiation, and removing the origin server response time.If your origin is slow in generating HTML and/or your user is far from the origin server then all your performance metrics will be affected.</p><p>Most HTML isn’t really dynamic. It needs to be able to change relatively quickly when the site is updated but for a huge portion of the web, the content is static for months or years at a time. There are special cases like when a user is logged-in (as the admin or otherwise) where the content needs to differ but the vast majority of visits are of anonymous users.</p>
    <div>
      <h3>Zero config edge caching revisited</h3>
      <a href="#zero-config-edge-caching-revisited">
        
      </a>
    </div>
    <p>The goal is to make updating content to the edge happen automatically. The edge will cache and serve the previous version content until there is new content available. This is usually achieved by triggering a cache purge to remove existing content. In fact using a combination of our <a href="https://www.cloudflare.com/integrations/wordpress/">WordPress plugin</a> and <a href="https://api.cloudflare.com/#zone-purge-files-by-url">Cloudflare cache purge API</a>, we already support <a href="https://support.cloudflare.com/hc/en-us/articles/115002708027-Cloudflare-WordPress-Plugin-Automatic-Cache-Management-">Automatic Cache Purge on Website Updates</a>. This feature has been in use for many years.</p><p>Building automatic HTML edge caching is more nuanced than caching traditional static content like images, styles or scripts. It requires defining rules on what to cache and when to update the content. To help with that task we introduced a custom header to communicate caching rules between Cloudflare edge and origin servers.</p><p>The Cloudflare Worker runs from every edge data center, the serverless platform will take care of scaling to our needs. Based on the request type it will return HTML content from Cloudflare Cache using <a href="https://developers.cloudflare.com/workers/runtime-apis/cache">Worker’s Cache API</a> or serve a response directly from the origin. Specifically designed custom header provides information from the origin on how the script should handle the response. For example worker script will never cache responses for authenticated users.</p>
    <div>
      <h3>HTML Caching rules</h3>
      <a href="#html-caching-rules">
        
      </a>
    </div>
    <p>With or without Cloudflare for WordPress plugin, HTML edge caching requires all of the following conditions to be met:</p><ul><li><p>Origin responds with 200 status</p></li><li><p>Origin responds with "text/html" content type</p></li><li><p>Request method is GET.</p></li><li><p>Request path doesn’t contain query strings</p></li><li><p>Request doesn’t contain any WordPress specific cookies: "wp-*", "wordpress*", "comment_*", "woocommerce_*" unless it’s "wordpress_eli" or "wordpress_test_cookie".</p></li><li><p>Request doesn’t contain any of the following headers:</p><ul><li><p>"Cache-Control: no-cache"</p></li><li><p>"Cache-Control: private"</p></li><li><p>"Pragma:no-cache"</p></li><li><p>"Vary: *"</p></li></ul></li></ul><p>Note that the caching is bypassed if the devtools are open and the “Disable cache” option is active.</p>
    <div>
      <h3>Edge caching with plugin</h3>
      <a href="#edge-caching-with-plugin">
        
      </a>
    </div>
    <p>The preferred solution requires a <a href="https://support.cloudflare.com/hc/en-us/articles/227634427-Using-Cloudflare-with-WordPress">configured Cloudflare for WordPress plugin</a>. We provide the following features set when the plugin is activated:</p><ul><li><p>HTML edge caching with 30 days TTL</p></li><li><p>30 seconds or faster cache invalidation</p></li><li><p>Bypass HTML caching for logged in users</p></li><li><p>Bypass HTML caching based on presence of WordPress specific cookies</p></li><li><p>Decrease load on origin servers. If a request is fetched from Cloudflare CDN Cache we skip the request to the origin server.</p></li></ul>
    <div>
      <h3>How is this implemented?</h3>
      <a href="#how-is-this-implemented">
        
      </a>
    </div>
    <p>When an eyeball requests a page from a website and Cloudflare doesn’t have a copy of the content it will be fetched from the origin. As the response is sent from the origin and goes through Cloudflare’s edge, Cloudflare for WordPress plugin adds a custom header: <code>cf-edge-cache</code>. It allows an origin to configure caching rules applied on responses.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6COdQPrdIxdu1UiJgsdyVd/015cf3f6aa598f70cfe089d74292a1b9/image4-1.png" />
            
            </figure><p>Based on the <a href="/improving-html-time-to-first-byte/">X-HTML-Edge-Cache</a> proposal the plugin adds a cf-edge-cache header to every origin response. There are 2 possible values:</p><ul><li><p>cf-edge-cache: no-cache</p></li></ul><p>The page contains private information that shouldn’t be cached by the edge. For example, an active session exists on the server.</p><ul><li><p>cf-edge-cache: cache, platform=wordpress</p></li></ul><p>This combination of cache and platform will ensure that the HTML page is cached. In addition, we ran a number of checks against the presence of WordPress specific cookies to make sure we either bypass or allow caching on the Edge.</p><p>If the header isn’t present we assume that the Cloudflare for WordPress plugin is not installed or up-to-date. In this case the feature operates without a plugin support.</p>
    <div>
      <h3>Edge caching without plugin</h3>
      <a href="#edge-caching-without-plugin">
        
      </a>
    </div>
    <p>Using the Automatic Platform Optimization feature in combination with Cloudflare for WordPress plugin is our recommended solution. It provides the best feature set together with almost instant cache invalidation. Still, we wanted to provide performance improvements without the need for any installation on the origin server.</p><p>We provide the following features set when the plugin is not activated:</p><ul><li><p>HTML edge caching with 30 days TTL</p></li><li><p>Cache invalidation may take up to 30 minutes. A manual cache purge could be triggered to speed up cache invalidation</p></li><li><p>Bypass HTML caching based on presence of WordPress specific cookies</p></li><li><p>No decreased load on origin servers. If a request is fetched from Cloudflare CDN Cache we still require an origin response to apply cache invalidation logic.</p></li></ul><p>Without Cloudflare for WordPress plugin we still cache HTML on the edge and serve the content from the cache when possible. The logic of cache revalidation happens after serving the response to the eyeball. <a href="https://developers.cloudflare.com/workers/learning/fetch-event-lifecycle#waituntil">Worker’s waitUntil() callback</a> allows the user to run code without affecting the response to the eyeball and is run in background.</p><p>We rely on the following headers to detect whether the content is stale and requires cache update:</p><ul><li><p>ETag. If the cached version and origin response both include ETag and they are different we replace cached version with origin response. The behavior is the same for strong and weak ETag values.</p></li><li><p>Last-Modified. If the cached version and origin response both include Last-Modified and origin has a later Last-Modified date we replace cached version with origin response.</p></li><li><p>Date. If no ETag or Last-Modified header is available we compare cached version and origin response Date values. If there was more than a 30 minutes difference we replace cached version with origin response.</p></li></ul>
    <div>
      <h3>Getting content everywhere</h3>
      <a href="#getting-content-everywhere">
        
      </a>
    </div>
    <p>Cloudflare Cache works great for the frequently requested content. Regular requests to the site make sure the content stays in cache. For a typical personal blog, it will be more common that the content stays in cache only in some parts of our vast edge network. With the Automatic Platform Optimization release we wanted to improve loading time for cache cold start from any location in the world. We explored different approaches and decided to use <a href="/introducing-workers-kv/">Workers KV</a> to improve Edge Caching.</p><p>In addition to Cloudflare's CDN cache we put the content into Workers KV. It only requires a single request to the page to cache it and within a minute it is made available to be read back from KV from any Cloudflare data center.</p>
    <div>
      <h3>Updating content</h3>
      <a href="#updating-content">
        
      </a>
    </div>
    <p>After an update has been made to the WordPress website the plugin makes a request to Cloudflare’s API which both purges cache and marks content as stale in KV. The next request for the asset will trigger revalidation of the content. If the plugin is not enabled cache revalidation logic is triggered as detailed previously.</p><p>We serve the stale copy of the content still present in KV and asynchronously fetch new content from the origin, apply possible optimizations and then cache it (both regular local CDN cache and globally in KV).</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1UGnbwtDfcNEmZXwV12urw/b1b58beeb7a4c5fee4ad8d5a8d755035/image5-2.png" />
            
            </figure><p>To store the content in KV we use a single namespace. It’s keyed with a combination of a zone identifier and the URL. For instance:</p>
            <pre><code>1:example.com/blog-post-1.html =&gt; "transformed &amp; cached content"</code></pre>
            <p>For marking content as stale in KV we write a new key which will be read from the edge. If the key is present we will revalidate the content.</p>
            <pre><code>stale:1:example.com/blog-post-1.html =&gt; ""</code></pre>
            <p>Once the content was revalidated the stale marker key is deleted.</p>
    <div>
      <h3>Moving optimizations to the edge</h3>
      <a href="#moving-optimizations-to-the-edge">
        
      </a>
    </div>
    <p>On top of caching HTML at the edge, we can pre-process and transform the HTML to make the loading of websites even faster for the user. Moving the development of this feature to our Cloudflare Workers environment makes it easy to add performance features such as improving <a href="/fast-google-fonts-with-cloudflare-workers/">Google Font loading</a>. Using Google Fonts can cause significant performance issues as to load a font requires loading the HTML page; then loading a CSS file and finally loading the font. All of these steps are using different domains.</p><p>The solution is for the worker to inline the CSS and serve the font directly from the edge minimizing the number of connections required.</p><p>If you read through the previous blog post’s implementation it required a lot of manual work to provide streaming HTML processing support and character encodings. As the set of worker APIs have improved over time it is now much simpler to implement. Specifically the addition of a streaming <a href="/html-parsing-2/">HTML rewriter/parser with CSS-selector based API</a> and the ability to suspend the parsing to <a href="/asynchronous-htmlrewriter-for-cloudflare-workers/">asynchronously fetch a resource</a> has reduced the code required to implement this from ~600 lines of source code to under 200.</p>
            <pre><code>export function transform(request, res) {
  return new HTMLRewriter()
    .on("link", {
      async element(e) {
        const src = e.getAttribute("href");
        const rel = e.getAttribute("rel");
        const isGoogleFont =
          src.startsWith("https://fonts.googleapis.com")

        if (isGoogleFont &amp;&amp; rel === "stylesheet") {
          const media = e.getAttribute("media") || "all";
          const id = e.getAttribute("id") || "";
          try {
            const content = await fetchCSS(src, request);
            e.replace(styleTag({ media, id }, content), {
              html: true
            });
          } catch (e) {
            console.error(e);
          }
        }
      }
    })
    .transform(res);
}</code></pre>
            <p>The HTML transformation doesn’t block the response to the user. It’s running as a background task which when complete will update kv and replace the global cached version.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1ShahwSObRage83CdWSfD0/528448594de96d64c0bb442f7e2db875/image1-6.png" />
            
            </figure>
    <div>
      <h3>Making edge publishing generic</h3>
      <a href="#making-edge-publishing-generic">
        
      </a>
    </div>
    <p>We are launching the feature for WordPress specifically but the concept can be applied to any website and content management system (CMS).</p> ]]></content:encoded>
            <category><![CDATA[Automatic Platform Optimization]]></category>
            <category><![CDATA[Birthday Week]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[WordPress]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Developers]]></category>
            <guid isPermaLink="false">TmM1U951RKeF4WEDa64px</guid>
            <dc:creator>Yevgen Safronov</dc:creator>
            <dc:creator>Sven Sauleau</dc:creator>
        </item>
    </channel>
</rss>