Article summary — original

Cloudflare Migrates cdnjs Entirely to Its Developer Platform As of June 23, 2026, open-source library CDN cdnjs runs entirely on Cloudflare's Developer Platform. The migration replaced an aging multi-provider pipeline with native Cloudflare services, resolving long-standing maintenance and observability bottlenecks while triggering platform-wide limit increases. cdnjs serves 9 billion requests daily across 330 data centers with a 98.6% cache hit rate, powering roughly 12% of all websites. The new pipeline replaced GCP Functions and VMs with Cloudflare Workflows, Containers, Queues, R2, and KV. Catalog files were copied as-is rather than regenerated to avoid breaking existing Subresource Integrity (SRI) hashes. Handling the migration led Cloudflare to expand platform limits, including subrequest caps and workflow step maximums. New Platform Architecture The updated architecture positions R2 as the primary storage source of truth for all library files, while KV is reserved for metadata such as package details, version lists, and SRI hashes. Delivery is fronted by Workers Cache, with all files mirrored to DigitalOcean Spaces for live fallback and disaster recovery. Ingestion runs on Cloudflare Workflows every ten minutes to detect new releases from npm and GitHub, using Rust-based containers for CPU-intensive compression, Durable Objects as completion counters, and Queues to coordinate tasks. Migration Obstacles and Platform Improvements A prior migration attempt failed because regenerating library packages changed output hashes, breaking user SRI guarantees. To prevent this, Cloudflare migrated the existing KV catalog to R2 verbatim. Moving millions of files exposed limits in Workers subrequests and Workflow step counts. In response, Cloudflare raised Worker subrequests up to 10 million on paid plans and expanded Workflow step caps from 1,024 to 10,000 by default (configurable up to 25,000). Future Considerations With the modern Workflows-and-Containers pipeline established, cdnjs is exploring whether to support transformed, browser-native ES Modules directly from publish without requiring external bundlers, a feature previously impractical under the legacy GCP setup.

Article summary — zh

cdnjs 全面迁移至 Cloudflare 开发者平台:日均 90 亿次请求的技术架构重构 开源前端 CDN 服务 cdnjs 宣布已完全迁移至 Cloudflare 开发者平台运行。此次重构不仅将此前运行在 GCP 上的发布流水线全面端到端迁移至 Workers、Workflows 和 R2 等产品,还促使 Cloudflare 针对平台多项核心配额限制进行了重大升级。 cdnjs 目前占据 JavaScript CDN 市场 48.3% 的份额,覆盖全球约 12% 的网站,日均处理 90 亿次请求,缓存命中率达 98.6%。 新架构将静态资源存储从 KV 迁移至 R2 作为单一事实来源,KV 仅保留包信息与哈希等元数据,并同步备份至 DigitalOcean Spaces 作为容灾及实时回退层。 入库流水线由 Cloudflare Workflows 调度,结合 Containers、Queues 与 Durable Objects 实现容错状态保存及并行文件的解压与压缩处理。 曾因重新处理旧包导致哈希不一致而遭遇回滚,团队最终决定以原样复制的方式迁移数百万文件,避免破坏已固定的 SRI 校验。 迁移过程中推动 Cloudflare 将 Workers 单次调用的子请求上限从 1,000 提升至最高 1,000 万,Workflows 默认步骤上限提升至 10,000(可配置至 25,000)。 项目现状与重构驱动力 cdnjs 诞生于 2011 年,由 Cloudflare 于 2019 年接管维护。尽管现代开发工具链已转向打包含和 ESM,但在大语言模型代码生成偏好以及免鉴权、零限流的驱动下,cdnjs 依然保持日均 90 亿次请求的体量。此前架构虽在分发层引入了 Workers 和 KV,但其发布和抓取流水线依然停留在包含 GCP Functions 和虚拟机的旧架构上,维护多平台部署与手动作日志排查成为主要的维护痛点。 基于 Cloudflare 全栈的新分发架构 新架构实现了全链路自托管。R2 取代 KV 成为文件内容的单一事实来源,支持大文件与 S3 兼容访问;KV 则专用于高频读取的版本与 SRI 哈希元数据。分发层引入了 Workers Cache 分层缓存,消除了以往的独立内部缓存层。此外,DigitalOcean Spaces 作为冷备与实时回退源,构成“缓存 → R2 → DigitalOcean”的请求链路,避免单点故障。 Workflows 与 Containers 驱动的发布流水线 内容摄入流水线由 Cloudflare Workflows 驱动,通过定时任务监测 npm 与 GitHub 更新,分发下载、解压、压缩及发布工作流,并利用持久化执行特性在故障时自动从中断步骤恢复。由于需在内存中整库缓冲处理,团队采用外部 Containers 运行 Rust 服务进行预压缩,借助 Queues 和 R2 事件唤醒等待的工作流,并通过轻量 Durable Object 计数器协调多子任务的并行发布。 迁移踩坑与平台配额的突破升级 团队早前曾尝试重新处理并压缩历史资源,但因压缩工具在不同版本下的非确定性导致生成的 SRI 哈希不匹配,可能导致现存网页资源失效,最终只能回滚并改为原样搬迁文件。在跨账户搬运数百万文件时,团队遭遇了 Workers 子请求与 Workflows 步骤数限制;随后 Cloudflare 将 Workers 子请求上限扩展至最高 1000 万次,并将 Workflows 步骤上限大幅提高至最高 25,000 步,解除限制的同时赋能所有平台用户。

Saved original article

As of June 23, 2026, cdnjs, one of the Internet's busiest open-source CDNs, is running exclusively on [Cloudflare’s Developer Platform](https://www.cloudflare.com/en-gb/developer-platform/). Along the way, cdnjs surfaced limits in the platform, and the platform grew to meet them. [cdnjs](https://cdnjs.com/) is a free, open-source content delivery network for JavaScript and CSS libraries. Instead of using a bundler or self-hosting jQuery, Bootstrap, or Lodash, you drop a \<script> tag pointing to [cdnjs.cloudflare.com](http://cdnjs.cloudflare.com) and the library loads from Cloudflare's edge, instantly, anywhere in the world, with no signup, no API keys, and no rate limits. It's the infrastructure behind a significant portion of “intro to JavaScript” tutorials, CodePen demos, and Stack Overflow answers. Community-driven, cdnjs is used on [roughly 12% of all websites](https://w3techs.com/technologies/details/cd-cdnjs), a 48.3% share of the JavaScript CDN market. It serves an average of 108,000 requests per second, 9 billion per day, across more than [330 Cloudflare data centers](https://www.cloudflare.com/en-gb/network/), with a 98.6% cache hit rate. Pretty cool, Internet! In 2011, when bundlers were exotic, npm was barely a year old, and "just drop a `<script>` tag" was how the web shipped JavaScript, Ryan Kirkman and Thomas Davis built cdnjs as a free, community-run mirror of every popular open-source library. Cloudflare stepped in to host it free of charge [months later](https://blog.cloudflare.com/cdnjs-community-moderated-javascript-librarie/), and took over project maintenance [in 2019](https://blog.cloudflare.com/an-update-on-cdnjs). Back then, Cloudflare didn't have a mature Developer Platform that could fully sustain the entire cdnjs ecosystem. Fifteen years and a lot of building blocks later, the platform is mature enough to run cdnjs end to end, on Workers, Workflows, D1, Queues, Workers Cache, R2, KV, and Containers. The web has changed beyond recognition from those days. We have ES Modules (ESM), the standardized import / export syntax browsers understand natively. We have import maps, Vite, Bun, Turbopack. We have AI assistants that scaffold entire apps in seconds. Bundlers are everywhere. So why does a CDN for `<script>` tags still serve 9 billion requests a day? One reason: LLMs love cdnjs. When ChatGPT, Claude, or Cursor scaffold a quick HTML demo, they reach for cdnjs because their training data is full of it. There have been 15 years of blog posts, GitHub READMEs, tutorial sites, and Q&A threads pointing to [cdnjs.cloudflare.com](http://cdnjs.cloudflare.com). The URL pattern is consistent and versions are immutable — exactly the kind of dependency a model can produce reliably without hallucinating. Every file on cdnjs has an SRI hash (*we're still working on ensuring all the existing stored hashes match reality due to bugs in the old system*), mirrors are auditable, and the whole project is open source. In a world increasingly worried about supply-chain attacks, an immutable, hash-verified mirror of well-known libraries is indispensable. *And it's free, forever, for everyone. No API keys. No rate limits. No "sign up to continue." That's a rare thing on today's Internet, and it's worth protecting.* We didn't migrate because cdnjs was slow. We migrated because we want to keep improving it. The previous architecture served users well: 98% cache hit, billions of requests, no outages. But internally, shipping anything new or fixing existing issues in how packages were processed was getting harder. Making a change meant coordinating deployments across GCP Functions, a VM, and Cloudflare. Observability was painful too. In 2020, [we migrated cdnjs to serverless](https://blog.cloudflare.com/migrating-cdnjs-to-serverless-with-workers-kv/), moving file serving onto Cloudflare Workers and KV, with a bare-metal origin as fallback. That change dramatically improved resilience and scalability, and let us pre-compress every asset with Brotli and gzip for smaller, faster responses, but only on the serving side. The publishing side — the pipeline that watches npm and GitHub for new library versions, downloads them, processes them, and writes the results so cdnjs can serve them — stayed on Google Cloud Platform (GCP). At the time, Cloudflare Workers were designed for fast, short-lived HTTP requests; they didn't yet have the building blocks for a long-running, multi-step pipeline that fetches large tarballs, runs CPU-heavy compression, and orchestrates work over hours. Workflows, Queues, Durable Objects, R2, and Containers didn't exist yet. So we built the publishing bot on what was available: a chain of GCP Functions, a VM running git-sync, and a [GitHub repository as the source of truth](https://github.com/cdnjs/cdnjs). It worked, but six years later, that architecture was showing its age. Here's a diagram of the previous architecture: The architecture had five pain points. The one that hurt most was observability: debugging meant stitching logs together by hand. We'll start there. The new cdnjs architecture runs entirely on Cloudflare’s Developer Platform. [R2](https://developers.cloudflare.com/r2/) is the single source of truth for file content. It has no practical size limit, so the files that couldn't fit in KV before, like source maps, big bundles, and font packs, now live alongside everything else. As a bonus, the S3 API makes the entire cdnjs catalog accessible to any S3 client. *Maintain a mirror? Open an issue on the [cdnjs repository](https://github.com/cdnjs/cdnjs) and we'll set you up with read-only credentials.* [KV](https://developers.cloudflare.com/kv) stores only metadata now: package info, version lists, SRI hashes. KV is built for high read volume with infrequent writes, which is exactly the shape of metadata access. In front of the Worker sits [Workers Cache](https://blog.cloudflare.com/workers-cache/), a tiered cache Cloudflare launched this year. Before, we relied on a separate internal caching layer between the edge and the Worker. That layer is gone now, replaced by one owned by the Developer Platform, the same platform that runs the rest of cdnjs. One less moving part! The new architecture also extends a long-standing partnership. [DigitalOcean](https://www.digitalocean.com/) has hosted the cdnjs website for years as a sponsor; now it hosts the storage too. Every file published to R2 is mirrored to DigitalOcean Spaces: architecturally a disaster-recovery copy, operationally also a live fallback. The serving worker reads through to it whenever R2 can’t return a file. The chain is cache → R2 → DigitalOcean, so R2 having a bad day doesn't take cdnjs down. *A Cloudflare-hosted origin still sits in the chain during the transition, but it will retire once the GitHub backfill lands in R2.* The ingestion pipeline is built on [Cloudflare Workflows](https://developers.cloudflare.com/workflows/). Every ten minutes, a cron job triggers PackageUpdatesWorkflow, which checks npm and GitHub for new versions. For each new version found, it spawns a DownloadPackageWorkflow that fetches the tarball into R2, then a ProcessingWorkflow per file that extracts, minifies, and compresses. Finally, PublishingWorkflow writes the results to R2 and KV and updates the [Algolia](https://www.algolia.com/) search index. Because Workflows provides durable execution, the state of each step is preserved. If anything fails — a network timeout, a compression error — the workflow resumes from the last successful step. The trickier piece is how we glue Workflows to the [external compression container](https://developers.cloudflare.com/containers/). We pre-compress text-based files to streamline the delivery process. Because our library processing algorithms require us to buffer the entire library (which might be very large) in memory we opted to use a container for the initial migration. We are now looking into making these algorithms support streaming to move this logic to the worker. The pipeline has two kinds of waiting: *Per file:* Each ProcessingWorkflow writes the uncompressed file to an R2 bucket, sends a job to a Queue, and hibernates. A Rust compression service running in the container picks it up, compresses it, and writes the result to another bucket. An R2 event notification wakes the workflow up so it can continue. *Per package:* The parent workflow needs to wait for all its file children before moving on to publish. A package with thousands of files means thousands of children running in parallel. We use a small Durable Object as a counter: a parent increments on each child it spawns, children decrement when they finish. The parent wakes up when the counter reaches zero. An overview of the new architecture, with R2 as the source of truth and Workflows running the pipeline: Designing the new architecture was one challenge. Migrating the existing catalog into it, without disturbing a single file already in the wild, was another. We'd actually tried this once before and had to roll back. The plan, back then, was to re-process old packages and write the results directly to R2, but the regenerated files didn't byte-match what KV had been serving. Minifiers and compressors aren't fully deterministic across versions, so the new outputs were correct but had different SRI hashes. For a CDN where users pin those hashes in their HTML, that's a serving break. So we rolled back, and now, we migrated the existing content from KV to R2 as-is instead of regenerating it. That decision shifted the problem from "re-process millions of files" to "copy millions of files between accounts, without missing any." And that's where we ran into the Workers subrequest limit, capped at 1,000 per invocation on paid plans. A package with thousands of files would burn through it in one go. Parallelizing didn't help, since every Worker hits the same ceiling. So we sharded the migration by package name and fanned the work out across many invocations via [Queues](https://developers.cloudflare.com/queues), whose at-least-once delivery guarantee meant no package could silently fall out of the migration. We hit two platform limits during the migration: 1,000 subrequests per Worker invocation and 1,024 steps per Workflow. Instead of just working around them, we asked the Workers and Workflows teams to raise them — which they did. Subrequests now go up to 10 million on paid plans; Workflows now default to [10,000 steps, configurable to 25,000](https://developers.cloudflare.com/changelog/post/2026-03-03-step-limits-to-25k/). The cdnjs pipeline runs on the same building blocks anyone can use: Workers, Workflows, R2, KV, Queues, Containers, and Durable Objects. The limits we hit are limits we lifted for everyone. If the Cloudflare Developer Platform can serve 9 billion requests a day and publish packages with hundreds of thousands of compressed, minified files, it can probably run whatever you're building. There's an obvious next question hiding in all of this: could cdnjs also serve modern, browser-native ES modules? The same packages, transformed on publish, ready to import without a bundler. The architecture doesn't rule it out. The Workflows-plus-Containers pattern that pre-compresses files today would work just as well for transforming them. We're not committing to it, but it's the kind of thing that's now possible to consider, which wasn't true a year ago. `git commit -m "with love" --author="cdnjs team"` *We follow every open issue on [GitHub](https://github.com/cdnjs) and we want your feedback. Don't hesitate to contribute and help make the Internet better for everyone.*