Building an Efficient and Secure Random Image API: A Practical Guide to Cloudflare Worker + R2 + KV
本文最后更新于 287 天前,其中的信息可能已经有所发展或是发生改变,如有失效可到评论区留言。
Article Abstract
To solve the tedious problem of manually changing blog background images, this article builds a random image API using the Cloudflare Worker + R2 + KV solution. This solution avoids the origin-pull dependency, high concurrency pressure, and cache management difficulties of traditional VPS solutions, utilizing Worker edge nodes to directly access images stored in R2 to achieve low-latency response under a serverless architecture. Version 1.0 implements the function of automatically selecting images based on device type, but has flaws such as crawler protection and rate limiting; version 2.0 optimizes access control through KV storage, improving security and performance. This solution is suitable for scenarios such as personal blogs, offering low cost and high availability advantages, and can be further optimized for user experience in combination with Cloudflare caching strategies.
Qwen3-14B · 2026-06-18

1 Introduction

I didn't originally plan to write this article. The reason I suddenly jumped the queue to write it is that I suddenly got a bit tired of the behavior of ”purely manually and periodically changing the blog's background image”~.

My blog's background images have always been manually changed by me periodically (images are stored on Cloudflare R2). Originally, changing the background image once every 1-2 months wasn't too much trouble, but as the background images I've used accumulate, I'm a bit reluctant to completely discard the ones I used before (after all, they were carefully selected by me), yet subjectively I'm also a bit unwilling to actively and manually change them back to previously used background images.

What to do? Why not just set up a random background? This way, it's like an emperor choosing a concubine by flipping a tablet—whichever one gets flipped is the one, and I don't have to struggle with the choice anymore~. No sooner said than done, and it also happens to make for a quick blog post~.

2 Part Two: Choice of Implementation Method

When building a random image API, you can choose a solution suitable for your actual conditions based on whether a ”VPS is required” as the judging criterion:

Solutions Requiring a VPS (Suitable for friends who have a VPS or use Cloudflare Tunnel)

If you already have a VPS (or use Cloudflare Tunnel to build your site), you can choose one of the following two methods:

• Local Storage: Images are stored directly on the VPS, and the server randomly returns an image via the API. Suitable for small-scale image libraries, with fast access speeds, but occupies VPS storage space.

• Cloud Storage: Images are stored in cloud storage such as Cloudflare R2, S3, OSS, etc., and the VPS handles the API logic. Suitable for large-scale image libraries, reducing the burden on the VPS, but access depends on external storage.

Solutions Not Requiring a VPS (Cloudflare Worker + Cloud Storage)

If you do not have a VPS, or wish to host completely on Cloudflare, you can choose:

• Cloudflare Worker + Cloud Storage (R2 / GitHub): Cloudflare Worker handles API requests, and images are stored in locations like Cloudflare R2, GitHub repositories, etc., directly returning the image URL. No server required, low maintenance cost, suitable for hosted services.

Note: Besides Cloudflare Worker, there are other options, but since I am already using Cloudflare, it was only natural to choose the Worker solution. Furthermore, from the perspective of global access, the fact that Worker can run on Cloudflare's global edge network is also a huge advantage.

3 Specific Implementation Steps

1 Solutions Requiring a VPS

1.1 Local Storage Method

This method is probably the easiest random image API solution to implement: you only need to install Nginx or Apacheas the web server software on your VPS and perform simple configurations to achieve this (note that if you choose Nginx, you need to ensure that PHP parsing is correctly configured (for deploying Nginx + PHP environment via Docker, please refer to the article:Docker Series: Single-container Nginx, Single-container PHP (one version) Multi-site Sharing; for deploying Nginx + PHP environment via Pagoda Panel, please refer to the article:Linux Panel Series: Deploying V-Sign via Source Code Based on Baota Panel); if you choose Apache, please confirm that the ”mod_php” module is installed):

1.1.1 Standard Version (No distinction between PC and Mobile)

Create a new web site, create a ”pc_img” directory in the root directory of the site (put the prepared wallpaper images in it), and an index.php file with the following content:


// Get images from the local pc_img folder$img_array = glob(__DIR__ . "/pc_img/*.{webp,gif,jpg,png}", GLOB_BRACE);// If there are no images in the directory, return 404

/var/www/html/
│── pc_img/    # 存放壁纸图片
│── index.php  # API 代码

http_response_code(404);”image.tangwudi.com“die(”No images found");”https://image.tangwudi.com“}

1.1.2 Advanced Version (Compatible with Mobile-Detect, distinguishing between PC and Mobile)

// Randomly select an imagehttps://github.com/serbanghita/Mobile-Detect$random_img = $img_array[array_rand($img_array)]; // 302 redirect to the imageheader("Location: " . str_replace(__DIR__, '', $random_img)); exit;?> Based on the above, a simple random image API can be implemented (the purpose of the index.php code isto randomly select an image and return it ). The final directory structure is as follows:/var/www/html/ │── pc_img/ # Store wallpaper images、│── index.php # API code、Redirection Assuming the domain name corresponding to the site is " ", then, visiting "" will randomly return an image from the "pc_img" directory. This method is slightly more advanced, mainly relying on a project on GitHub: , which is a

PHP device detection library

wget https://raw.githubusercontent.com/serbanghita/Mobile-Detect/master/Mobile_Detect.php -O Mobile_Detect.php

determine the visitor's device type

isMobile() && !MobileDetect->isTablet();

// 选择文件夹(PC / 移动端不同壁纸)folder = is_mobile ? "mobile_img" : "pc_img";

// 获取本地对应文件夹中的图片img_array = glob(__DIR__ . "/folder/*.{webp,gif,jpg,png}", GLOB_BRACE);

// 如果目录中没有图片,返回 404
if (!img_array) {
    http_response_code(404);
    die("No images found");
}

// 随机选择一张图片
random_img =img_array[array_rand(img_array)];

// 302 重定向到该图片
header("Location: " . str_replace(__DIR__, '',random_img));
exit;
?>

(such as

/var/www/html/
│── pc_img/        # 存放 PC 端壁纸
│── mobile_img/    # 存放移动端壁纸
│── Mobile_Detect.php # 设备检测库
│── index.php      # API 代码

). It is based on

1.2 Cloud Storage Method

1.2.1 Overview

User-Agent parsing

1.2.2 Standard Version (No distinction between PC and Mobile)

, and can be used for:

  1. mobile adaptationdynamic content adjustment
  2. and other scenarios. It helps developersaccurately distinguish between PC, mobile, and tablet
  3. , making it highly suitable for。
  4. responsive design, content optimization, API adaptationand other scenarios, such as the use case in this article.

You still need to create a new web site, but this time, in addition to creating a "pc_img" directory in the root directory of the site (to put wallpaper images for PC access), you also need to create a "mobile_img" directory (to put wallpaper images for mobile access) and a Mobile_Detect.php file. This file can be downloaded using the following command:

以 Cloudflare R2 wget https://raw.githubusercontent.com/serbanghita/Mobile-Detect/master/Mobile_Detect.php -O Mobile_Detect.php”https://image.tangwudi.com/“An index.php file with the following content:The final directory structure is as follows:/var/www/html/

https://image.tangwudi.com/img1.jpg
https://image.tangwudi.com/img2.png
https://image.tangwudi.com/img3.webp

│── pc_img/ # Store PC wallpapers

│── mobile_img/ # Store mobile wallpapers

https://image.tangwudi.com/img1.jpg
https://image.tangwudi.com/img2.png
https://image.tangwudi.com/img3.webp

│── Mobile_Detect.php # Device detection library


Code logic:

• Read the image list in pc_img.txt.

• If pc_img.txt is empty, return 404.

• Randomly select an image, and 302 redirect to the URL of that image.

Accessing “https://image.tangwudi.com/“, will randomly return an image URL from pc_img.txt

Directory structure:

/var/www/html/
│── pc_img.txt    # 存放云存储的图片 URL
│── index.php     # API 代码

Step 4: Regularly update pc_img.txt (Optional)

If your cloud storage images are updated frequently, you can use a cron job to automatically sync img.txt。

Linux cron job example (applicable to AWS S3)

Run the following command on the VPS to automatically write the bucket's image URLs to img.txt:

aws s3 ls s3://your-bucket-name/ --recursive | awk '{print "https://image.tangwudi.com/"$NF}' > /var/www/html/pc_img.txt

Then add a cron job in crontab -e to update hourly:

0 * * * * /path/to/script.sh

If you are using Cloudflare R2, you can write a Python script to periodically call the r2 API to get the image list, and then update img.txt.

1.2.3 Advanced Version (Compatible with Mobile-Detect, distinguishing between PC and Mobile)

Compared to the basic version, the advanced version requires an additional mobile_img.txt, specifically used to store URLs of wallpaper images suitable for mobile devices. At the same time, just like the advanced version of the local storage method, it also requires an additional Mobile_Detect.php, and the content of index.php is also different:

isMobile() && !MobileDetect->isTablet();

// 选择文件(PC / 移动端不同壁纸)filename = is_mobile ? "mobile_img.txt" : "pc_img.txt";

// 文件不存在,返回 404
if (!file_exists(filename)) {
    http_response_code(404);
    die("File not found");
}

// 读取图片链接(跳过空行和注释)
pics = array_filter(file(filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES), function(line) {
    return substr(line, 0, 1) !== '#';
});

// 如果图片列表为空,返回 404
if (empty(pics)) {
    http_response_code(404);
    die("No images found");
}

// 随机选择一张图片random_img = pics[array_rand(pics)];

// 302 重定向到该图片
header("Location: " . $random_img);
exit;
?>

The directory structure is as follows:

/var/www/html/
│── pc_img.txt       # 存放 PC 端壁纸 URL
│── mobile_img.txt   # 存放移动端壁纸 URL
│── Mobile_Detect.php # 设备检测库
│── index.php        # API 代码

The access method for the advanced version is the same as the basic version, but it will automatically select the image URL from ”pc_img.txt” or “mobile_img.txt” based on the device type.


Use VPS + PHP to implement a random image API relies on index.php to handle each request: whenever a user requests an image, the request must go back to the origin VPS, which brings the following issues:

High VPS connectivity requirements

• Origin-pull dependency: Every request must pull from the origin VPS to get the image. If there is an issue with the VPS (such as network failure, excessive server pressure, etc.), it will cause request failure or latency. Besides, who can guarantee that all users can access the VPS quickly?

• Bandwidth bottleneck: If the traffic is heavy and the images are large, the VPS bandwidth will become a bottleneck, especially when using local storage and the CDN is not properly configured.

High performance pressure under high concurrency

• Every request is handled by the VPS, which can easily cause VPS performance overload when traffic is high.

• When traffic suddenly surges, the VPS hardware and bandwidth may be insufficient, leading to response timeout 或 or crash。

Caching issues

• Even if the images are cached using a CDN, each random image selection still needs to be processed by the PHP backend. The CDN will only cache the finally returned image URL, not the decision logic of index.php.

• Whenever images change,cache purging needs to be managed by pulling from the origin VPS. As a result, for large-scale updates or high-concurrency requests, this will cause a lower cache hit rate and affect performance.

VPS cost

• Under high concurrency, if the VPS requires more bandwidth, processors, and memory, it will face increased costs.

Therefore, for scenarios with low traffic and low high-availability requirements (such as background images for personal blogs), the VPS-based random image API solution is acceptable. However, once traffic increases, or there are high requirements for high availability or response speed, this solution is no longer suitable.


Note 1: The above VPS-based solution, in addition to requiring familiarity with conventional website building methods, also requires familiarity with reverse proxy configuration (public IP website building) or having a Cloudflare account and being able to complete conventional function configurations (Cloudflare tunnel website building). For those who are not familiar with reverse proxy configuration, you can refer to the articles:Linux Panel Series: Configuring Reverse Proxy and Publishing Using Non-443 Ports(Using Baota Panel to implement reverse proxy),Docker Series: Building Your Own Reverse Proxy Based on NPM Using Docker(Using NPM to implement reverse proxy); for those who are not familiar with Cloudflare tunnel configuration, you can refer to the articles:Home Data Center Series: Using Tunnel Technology to Enable Home Broadband Without Public IP to Leverage Cloudflare for Free to Achieve Rapid Website Setup (Recommended)。

Note 2: The above part is just a habitual summary after I read the conventional VPS-based random image API tutorials on the Internet. It is not the focus of this article, nor do I recommend it, so I just glossed over it.

Note 3: I don't know why, but there are issues with the PHP code blocks after converting them to HTML. I spent a long time trying to fix it but failed, and I'm too lazy to bother anymore. Forget it, it's not the focus of this article anyway, please bear with me.

2 Solutions Not Requiring a VPS (Cloudflare Worker + R2)

2.1 Overview

This solution is actually an improved version of the previous ”VPS + Cloud Storage” solution. Although it doesn't require a VPS, it does require a technology that can replace the role of ”index.php” in the ”VPS + Cloud Storage” solution (randomly returning the URL of an image). My image hosting was originally built using Cloudflare R2, so using Cloudflare Worker to implement the function of ”index.php” is very reasonable:

image.png

image.png


The article mentioned earlier that the VPS + PHP-based random image API solution faces some issues, whereas the Cloudflare Worker-based solution does not have these problems and instead offers many advantages in these aspects:

Decentralization, no need to return to origin

• Worker runs entirely on edge nodes, and can directly respond to requests without relying on a remote VPS.

• Images are stored in Cloudflare R2 or other cloud storage services, and the Worker will fetch and return images directly from these locations without returning to the origin VPS.

Scalability under high concurrency

• Worker is stateless and does not rely on server hardware resources. Cloudflare's edge network automatically performs Load balancingscaling, so even if the request volume surges, performance will not be affected.

• Worker is billed by request volume, which in most cases is more cost-effective than a VPS, especially for application scenarios with high traffic but low computation (with a free quota of 100,000 requests per day, as long as you don't encounter a DDoS attack, a regular personal blog will definitely never run out~).

Excellent caching mechanism

• Cloudflare itself provides a powerful caching mechanism, which can cache images and return results at the CDN node layer, reducing the computational pressure on the Worker and the need to return to the origin.

• Images can be cached for a longer time without needing to request the origin every time,significantly improving performance。

Flexible image storage and optimization

• Images can be stored in R2 or other cloud storage, and utilize Cloudflare's Polish and Mirage features for automatic optimization and compression (requires a Pro subscription), without manual processing.

• Worker can cooperate with caching strategies for intelligent caching and image updates, reducing duplicate requests.

Low cost, high availability

• Worker's billing model is based on request volume and resource consumption , making it suitable for application scenarios with irregular traffic.

• It does not require high-performance VPS or load balancers to handle large traffic, reducing operation and maintenance costs.

Conclusion: It's free and more powerful, what more could you ask for?


However, unlike the ”VPS + Cloud Storage” scenario, files on R2 cannot be edited directly. If we still follow the idea of img.txt in the ”VPS + Cloud Storage” scenario, it would be very troublesome: every time a new image is added, we would need to re-edit and upload the pc_img.txt file (and also edit and upload the mobile_img.txt file in v2.0). Although this editing and uploading behavior can be made less troublesome through methods like rclone or alist, it is still too torturous for a lazy person like me. Therefore, we need to change our approach:

  • Considering that the functions of the Worker method are all completed by code, detecting the terminal type does not require a separate ”Mobile_Detect.php” file like the VPS method. Therefore, v1.0 under the ”Cloudflare Worker + R2" solution should include the terminal detection function (correspondingly, the image folders for v1.0 will also need to be 2).
  • The ideal management and operation effect is to use folder management: create directories ”pc_img” and ”mobile_img” on R2, upload wallpapers suitable for PC and mobile to the two directories respectively, and then the Worker will automatically select a random image from the two directories based on the visitor's terminal device type and return the image URL to the visitor. No extra operations (like txt files, etc.) are needed.

Actually, in the VPS + Local Storage method introduced earlier in the article, folder-based management and operation was also used. However, that was because PHP could directly obtain the file contents under the pc_img and mobile_img directories (through the local file system), while Cloudflare Worker can directly obtain the file contents in the R2 bucket (which requires binding the R2 bucket in the Worker ”Settings”), which is why this method can be adopted.

Note 1: If the cloud storage is not R2 (for example, a GitHub repository), then trying to use this ”folder method” for management and operation will be much more troublesome. This is because the Worker can only obtain the image content in the GitHub repository through API calls. Although it seems like just one extra step, the complexity of the code and code debugging will increase significantly, making it far less convenient than ”Worker + R2". Of course, you can also take a step back and use the txt file method, which just requires a bit more effort in daily use and is slightly less elegant.

Note 2: Cloudflare Worker is a serverless computing platform provided by Cloudflare that allows developers to run JavaScript code on Cloudflare's global edge network. It is based on the V8 engine, supports fast processing of HTTP requests, requires no server management, and is suitable for scenarios such as API proxies, content rewriting, and static resource distribution. Since Worker runs on Cloudflare's edge nodes, it can provide low-latency, high-availability services, and can be combined with KV storage, Durable Objects, and other features to expand application capabilities. For more knowledge and configuration details about Cloudflare Worker, please refer to the article:Home Data Center Series Cloudflare Tutorial (7): Introduction to CF Worker Functions and Practical Operation, Verification, and Related Technical Principles of Implementing ”Beggar's Version of APO for WordPress” Based on Worker to Accelerate Website Access。

2.2 Preparation

2.2.1 Create a dedicated bucket on R2 for random wallpapers (optional but highly recommended)


Actually, I personally recommend creating a dedicated bucket for random wallpapers, even for friends who have already used R2 as an image host and have an existing bucket. It's not absolutely necessary, but in addition to setting up domain-based routing when creating a new Worker (generally using the custom domain of the bucket, as a new bucket + new domain setup is more convenient), for the Worker itself to work properly, it requires correct configuration of Page Rules, Cache Rules, WAF Custom Rules, etc., which have a higher priority in the traffic sequence than the Worker (for concepts related to Cloudflare traffic sequence, please refer to the article:Home Data Center Series: Cloudflare Tutorial (Part 2) - Brief Introduction to the Functions of Technical Nodes in CF's Overall Solution Traffic Sequence). If you directly use an existing bucket, I worry that friends who are not very familiar with Cloudflare traffic priority and these rule configurations might overlook something, resulting in the random image effect failing to work. Therefore, the best method is to create a new bucket dedicated to the random wallpaper function and use a new third-level domain, which can largely avoid the impact of previous rule configurations. For example, I created a new bucket named wallpaper.


Follow the image tutorial below to operate:

image.png

image.png

image.png

Select the ”Settings” tab:
image.png

Configure custom domain:
image.png

image.png

Then click ”Connect Domain” to generate an R2 type record in DNS:
image.png

2.2.2 Create KV

Note: If you do not need the rate limiting feature (to prevent malicious traffic abuse) in the subsequent v2.0 and v3.0 versions, this step can be skipped.

image.png

image.png

As mentioned earlier, to use worker to control edge caching, you need to cooperate with the KV feature. Therefore, the first step is to create a KV namespace. Follow the process below to create the KV space.
image.png

The reason KV was chosen instead of the D1 database is that if it is only for temporarily storing IP access counts (cleared and restarted after 60 seconds), KV is sufficient and fast. If there are more complex requirements, such as recording IP access counts and providing query functions, the D1 database would be needed.

2.2.3 Upload wallpaper images to the R2 wallpaper bucket

• PC wallpapers are stored in the pc_img/ directory

• Mobile wallpapers are stored in the mobile_img/ directory

The example directory structure is as follows**:

pc_img/
│── pc1.jpg
│── pc2.png
│── pc3.webp

mobile_img/
│── mobile1.jpg
│── mobile2.png
│── mobile3.webp

Note: The image names above are just for demonstration; in practice, the names can be anything and will not affect the result.

2.3 Implementation of v1.0 Worker Code

2.3.1 Create Worker

image.png

image.png

image.png

2.3.2 Edit Code

image.png

Copy and paste the following v1.0 code into the Worker and deploy it:

export default {
  async fetch(request, env, ctx) {
    const bucket = env.WALLPAPER_BUCKET; // 绑定 Cloudflare R2 存储桶
    const url = new URL(request.url);
    const typeParam = url.searchParams.get("type"); // 获取 URL 参数 ?type=pc 或 ?type=mobile
    const userAgent = request.headers.get('User-Agent') || ''; // 获取访问者的 User-Agent 信息

    // 判断是否是 PC 设备
    const isPC = /Windows NT|Macintosh|X11|Linux/i.test(userAgent);
    // 判断是否是移动设备
    const isMobile = /Android|iPhone|iPad|iPod|webOS|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);

    // 默认从 pc_img 文件夹获取壁纸
    let folder = "pc_img/";

    // 如果 URL 参数指定了 type=mobile,则使用 mobile_img
    if (typeParam === "mobile") folder = "mobile_img/";
    // 如果 URL 参数指定了 type=pc,则强制使用 pc_img
    else if (typeParam === "pc") folder = "pc_img/";
    // 如果没有 URL 参数,则根据 User-Agent 判断设备类型
    else if (!isPC && isMobile) folder = "mobile_img/";

    try {
      //  获取 R2 存储桶中对应文件夹的图片列表
      const objects = await bucket.list({ prefix: folder });
      const items = objects.objects;

      // 如果该目录下没有图片,返回 404
      if (items.length === 0) return new Response('No images found', { status: 404 });

      //  随机选择一张图片
      const randomItem = items[Math.floor(Math.random() * items.length)];

      // 生成图片的 URL
      const imageUrl = `https://wallpaper.tangwudi.com/${randomItem.key}`;

      //  302 重定向到图片 URL
      return Response.redirect(imageUrl, 302);
    } catch (error) {
      console.error('Error:', error); // 记录错误信息
      return new Response('Internal Server Error', { status: 500 }); // 发生错误时返回 500
    }
  }
};

As shown in the figure below:

image.png


The main function of this code is to ”randomly return an image URL from the Cloudflare R2 bucket based on the type of accessing device (PC or mobile)“, and its workflow is as follows:

1️. Identify user device

• The code will first check the visitor's User-Agent(i.e., the browser's identity information).

• If it is a mobile phone or tablet(iPhone, Android, etc.), it will select a wallpaper from the mobile_img/ folder.

• Otherwise, it defaults to selecting a wallpaper from the pc_img/ folder.

2️. Get file list

• The code sends a request to the Cloudflare R2 bucket to retrieve the list of all images under the corresponding folder.

• If no images are found in the folder, it returns 404 Not Found (indicating no images are available in the current category).

3️. Randomly select an image

• The code willrandomly pickone from the image list.

• Then it attempts to retrieve the specific content of this image from the R2 bucket.

• If this image cannot be found, it returns 404 Not Found (possibly due to a missing file).

4️. Set the correct image format

• The code will check if the R2 bucket has recorded this image's Content-Type(such as image/png or image/jpeg).

• If not found, it defaults to treating it as image/jpeg to ensure the browser can load it correctly.

5️. Return the image 🚀

• The code finally returns the image URL to the user, allowing them to see the wallpaper normally.


2.3.3 Bind R2 Bucket

image.png

image.png

Fill in the variable name as follows, making sure not to make a mistake:

WALLPAPER_BUCKET

As shown in the figure below:

image.png

2.3.4 Add Entry Route

image.png

image.png

image.png

Once finally completed:

image.png

2.3.5 Final Result

Under the premise that every configuration step above is correct, and there are no errors in the page rules, cache rules, and WAF rules (if the custom domain of the bucket is a newly created domain, it is generally fine), the access request can hit the worker, and the normal effect is:

• Access https://wallpaper.tangwudi.com, according to the type of access terminal, returns a corresponding random wallpaper URL。

• Access https://wallpaper.tangwudi.com?type=mobile, specifically returning mobile wallpaper URL。

• Access https://wallpaper.tangwudi.com?type=pc, forcibly returning PC wallpaper URL。

After the test succeeded, I was happy for a while, but upon closer inspection, I felt that this code only implemented the most basic random image API function. Although it might be enough for average friends, if you really delve into it, you will find quite a few problems, and it feels like just a semi-finished product.

2.3.6 List of Issues

Blocking crawlers was not considered

Although custom rules can be used in Cloudflare WAF to filter crawlers, I believe most friends might not be familiar with how to configure them (although I strongly recommend everyone to study Cloudflare WAF's custom rules, which is a free and powerful feature), so it is best to have simple protection directly in the code.

Rate limiting was not considered

When adding a route earlier in the article, there is an option called ”Failures”, which is actually the handling behavior when the worker's daily free usage exceeds 100,000 times. There are ”Fail closed (block)” and ”Fail open (continue)”. In the screenshot of the previous article, I mentioned: for the v1.0 version, choosing either behavior makes no difference. Why is that?

Because for general websites, ”Fail open (continue)” is equivalent to directly returning to the origin, and the data can still be accessed, though it might be slower due to the lack of caching. But for the random image API, ”https://wallpaper.tangwudi.com“ response relies entirely on the worker. Once the limit is exceeded, the worker stops working altogether. At this point, even if it returns to the origin, R2 will not respond, so choosing either option is meaningless.

Note 1: Regarding the issue of exceeding limits, some friends might say: ”How could it exceed the limit? With a daily quota of 100,000 requests, an awesome personal blog with a daily traffic of 1,000 only uses 1,000, so it can never be used up.” Generally speaking, that's true, but that's only when you haven't encountered malicious traffic scraping. My image hosting site didn't have any access restrictions before, and as a result, it was scraped for 5T of traffic within 2 hours early one morning, forcing me to directly shut down direct access to the image hosting. Having said all this, it actually means that rate limiting needs to be considered in the code: if the number of accesses from a certain IP within a unit of time exceeds the threshold, it will no longer respond.

Note 2: Frankly speaking, the rate limiting of the v2.0 version code can only guard against some malicious traffic scraping access, but against a real DDoS attack, it will only die a second later~, so it is still necessary to properly configure Cloudflare's DDoS attack protection related functions.

Cache optimization was not considered

Although changing the background image every time you refresh or switch pages feels cool, from the webmaster's perspective, it may not be a good thing: each refresh means the worker will process a request, and the browser frequently accessing the Worker in a short period of time will increase unnecessary resource consumption. Moreover, from the user experience perspective, having a background image loading process every time may not necessarily be pleasant, so it is best to add moderate browser local caching in the code (actually, this can also be achieved through page rules or cache rules, but for the same reason as WAF custom rules, I believe most friends may not set them).

Worker routing settings based on query parameters for API calls are too coarse

The API options provided by the v1.0 version are ”?type=pc” and ”?type=mobile”. However, since the worker's entry route cannot carry query parameters, to support the ”?type=” option, its route can only use ”wallpaper.tangwudi.com/* “, but in this way, the worker has to process requests for all paths (including malicious requests).

Note: Of course, you can use WAF's custom rules to intercept here, but again, I believe most friends may not set them, so it is best to achieve a certain level of prevention at the worker code level.

Other issues

In addition, the v1.0 version script is relatively simple, does not include error logs and debugging support, and is not complete from the perspective of code.

Based on the above issues, the v2.0 version was born.

2.4 Implementation of v2.0 Worker Code

In the worker, replace the previous v1.0 version code with the following v2.0 version code and deploy it:

export default {
  async fetch(request, env, ctx) {
    const bucket = env.WALLPAPER_BUCKET; // 绑定 Cloudflare R2 存储桶
    const url = new URL(request.url);
    const pathname = url.pathname.toLowerCase(); // 获取请求路径(忽略大小写)

    // **屏蔽常见爬虫工具**
    const userAgent = (request.headers.get("User-Agent") || "").toLowerCase();
    const cfRay = request.headers.get("cf-ray") || "unknown";

    const botPatterns = [
      "curl", "wget", "httpie", "python-requests", "scrapy", "postmanruntime",
      "go-http-client", "java/", "libwww-perl", "okhttp", "python-urllib",
      "apache-httpclient", "httpclient", "lwp::simple", "mechanize", "aiohttp",
      "axios", "reqwest", "puppeteer", "headlesschrome", "phantomjs"
    ];

    if (!userAgent.trim()) {
      console.log(`[BLOCKED] Empty User-Agent - cf-ray: {cfRay}`);{userAgent} - cf-ray: {cfRay}`);
      return new Response("Forbidden: Bot Detected", { status: 403 });
    }

    // **确定文件夹(PC / Mobile)**
    let folder = "pc_img/"; // 默认 PC

    if (pathname.endsWith("fallback_mobile.jpg")) {
      folder = "mobile_img/";  // 强制移动端壁纸
    } else if (pathname.endsWith("fallback_pc.jpg")) {
      folder = "pc_img/";  // 强制 PC 端壁纸
    } else {
      // **`fallback.jpg` 需要根据 User-Agent 识别**
      const isMobile = /iphone|ipod|android|blackberry|iemobile|opera mini/.test(userAgent);
      folder = isMobile ? "mobile_img/" : "pc_img/";
    }

    console.log("Requested Path:", pathname);
    console.log("Selected Folder:", folder);

    // **初始化内存缓存(Map)**
    const rateLimitCache = new Map(); // 用于缓存速率限制数据

    // **速率限制(60 秒内最多 10 次请求)**
    const clientIP = request.headers.get("CF-Connecting-IP") || "unknown";
    const rateLimitKey = `rate-limit-{clientIP}`;

    // 尝试从内存缓存获取速率限制数据
    let currentCount = rateLimitCache.get(rateLimitKey);
    
    if (!currentCount) {
      // 如果缓存中没有,尝试从 KV 存储获取
      try {
        currentCount = await env.RATE_LIMIT_KV.get(rateLimitKey);
        if (currentCount) {
          currentCount = parseInt(currentCount);
        } else {
          currentCount = 0;
        }
      } catch (error) {
        console.error("Error fetching rate limit from KV:", error);
        // 如果 KV 读取失败,默认从 0 开始
        currentCount = 0;
      }
    }

    const newCount = currentCount + 1;

    // 如果超出速率限制,返回 429 错误
    if (newCount > 10) {
      return new Response("Too Many Requests", { status: 429 });
    }

    // 更新内存缓存和 KV 存储
    rateLimitCache.set(rateLimitKey, newCount);
    try {
      // 如果 KV 存储存在,则更新 KV 存储中的数据
      await env.RATE_LIMIT_KV.put(rateLimitKey, newCount, { expirationTtl: 60 });
    } catch (error) {
      console.error("Error updating rate limit in KV:", error);
      // 即使 KV 更新失败,也不影响请求继续处理
    }

    try {
      // **获取 R2 存储桶中的文件列表**
      const objects = await bucket.list({ prefix: folder });
      const items = objects.objects;

      if (items.length === 0) {
        return new Response("No images found", { status: 404 });
      }

      // **随机选择一张图片**
      const randomItem = items[Math.floor(Math.random() * items.length)];
      if (!randomItem) {
        return new Response("No valid images", { status: 404 });
      }

      const imageUrl = `https://wallpaper.tangwudi.com/${randomItem.key}`;

      // **设置缓存策略**
      const headers = new Headers();
      headers.set("Cache-Control", "public, max-age=600"); // 浏览器缓存 10 分钟
      headers.set("CDN-Cache-Control", "public, max-age=604800"); // Cloudflare CDN 缓存 7 天
      headers.set("ETag", randomItem.etag);

      // **302 重定向到图片 URL**
      return Response.redirect(imageUrl, 302);
    } catch (error) {
      console.error("Error in Worker:", error);
      return new Response("Internal Server Error", { status: 500 });
    }
  }
};

Then you need to bind the previously created KV namespace (KV is needed to store the statistics of IP access counts):

image.png

Fill in the variable name as follows, and be careful not to make a mistake:

RATE_LIMIT_KV

As shown in the figure below:

image.png

The resources bound upon final completion include the KV namespace in addition to the R2 bucket:
image.png

Compared with the v1.0 version code above, this v2.0 version code has made many important improvements and enhancements:

Bot Detection

v1.0 version codedoes not have crawler restrictions and just directly processes requests, which may encounter a large amount of crawler traffic or malicious traffic scraping behavior,v2.0 version codecan provide a certain level of protection:

 // **Block common crawler tools** {cfRay}`);{userAgent} - cf-ray: ${cfRay}`);

• Problem Solved: By matching the User-Agent field to determine whether it is a common crawler tool, if it is a crawler, it returns 403 Forbidden, which can effectively prevent unwelcome automated access.

Rate Limiting

v1.0 version code, requests from any IP are unrestricted, which may encounter abusive requests,v2.0 version codehas gained a certain level of protection capability:

    // **初始化内存缓存(Map)**
    const rateLimitCache = new Map(); // 用于缓存速率限制数据

    // **速率限制(60 秒内最多 10 次请求)**
    const clientIP = request.headers.get("CF-Connecting-IP") || "unknown";
    const rateLimitKey = `rate-limit-${clientIP}`;

    // 尝试从内存缓存获取速率限制数据
    let currentCount = rateLimitCache.get(rateLimitKey);
    
    if (!currentCount) {
      // 如果缓存中没有,尝试从 KV 存储获取
      try {
        currentCount = await env.RATE_LIMIT_KV.get(rateLimitKey);
        if (currentCount) {
          currentCount = parseInt(currentCount);
        } else {
          currentCount = 0;
        }
      } catch (error) {
        console.error("Error fetching rate limit from KV:", error);
        // 如果 KV 读取失败,默认从 0 开始
        currentCount = 0;
      }
    }

    const newCount = currentCount + 1;

    // 如果超出速率限制,返回 429 错误
    if (newCount > 10) {
      return new Response("Too Many Requests", { status: 429 });
    }

    // 更新内存缓存和 KV 存储
    rateLimitCache.set(rateLimitKey, newCount);
    try {
      // 如果 KV 存储存在,则更新 KV 存储中的数据
      await env.RATE_LIMIT_KV.put(rateLimitKey, newCount, { expirationTtl: 60 });
    } catch (error) {
      console.error("Error updating rate limit in KV:", error);
      // 即使 KV 更新失败,也不影响请求继续处理
    }

• Problem Solved: By introducing KV storage and a rate limiting mechanism to limit the same IP to only 10 requests within 60 seconds. If this number is exceeded, a 429 status code (Too Many Requests) is returned, effectively preventing malicious requests and abuse, and responding normally again after the 60-second statistics are cleared.

• Enhanced Features: Rate limiting is calculated individually for each IP, restricting the access frequency, preventing the same IP from initiating too many requests, and to some extent guarding against some malicious traffic scraping behaviors.

Cache Control

v1.0 version codedoes not set ETag and Cache-Control headers when returning responses, and the caching strategy relies entirely on the default behavior of Cloudflare or the browser,v2.0 version codehas optimized the cache:

      // **设置缓存策略**
      const headers = new Headers();
      headers.set("Cache-Control", "public, max-age=600"); // 浏览器缓存 10 分钟
      headers.set("CDN-Cache-Control", "public, max-age=604800"); // Cloudflare CDN 缓存 7 天
      headers.set("ETag", randomItem.etag);

• Problem Solved: The Cache-Control header is set, specifying that the response can be cached by the browser for 10 minutes, which helps reduce server load and works with ETag to achieve more precise cache control; at the same time, by setting CDN-Cache-Control, the image can exist on Cloudflare's edge cache for 7 days.

• Enhanced Features: Although Cache-Control can reduce access frequency, it also allows the background image to be updated within a certain period. Combined with ETag and edge caching, the user's browser and Cloudflare will handle caching more intelligently.

However, compared to configuring caching in the worker, I sincerely recommend using Cloudflare's Cache Rules (or Page Rules, but using Page Rules for caching is a bit wasteful and unnecessary) to achieve this, which is simple and clear, as shown below:

image.png


If Cloudflare's image optimization features, such as Polish and Mirage, are enabled at the same time, it may cause the 10-minute local browser caching policy to fail:

• Polish 和 Mirage Both may change the content or format of the image, which can cause browser and CDN (Cloudflare) cache misses, thereby leading to a new image being loaded every time.

• Polish It may result in different image formats (such as WebP and the original format), and they may differ with each request, leading to cache misses.

• Mirage This may return different versions of the image depending on device and network conditions, leading to cache inconsistency.


Remove the code's judgment based on the query parameter ”?type=” and change it to static path judgment

From a security perspective, using static paths (such as/fallback_pc.jpg 和 /fallback_mobile.jpg) instead of query parameters can make Worker routing cleaner (wallback.tangwudi.com/*is too broad): only these three specific routes are needed to clearly distinguish device types, avoiding reliance on query parameters:

image.png

At the same time, the methods for accessing the random image API, forcing the return of PC wallpapers, and forcing the return of mobile wallpapers have changed as follows:

• Access https://wallpaper.tangwudi.com/fallback.jpg, returns a “corresponding random wallpaper URL” based on the accessing device type (the reason for this change is explained below).
• Access https://wallpaper.tangwudi.com/fallback_mobile.jpg, forces the return of the ”mobile wallpaper URL”.

• Access https://wallpaper.tangwudi.comfallback_pc.jpg, forces the return of the ”PC wallpaper URL”.

This not only reduces the risk of malicious users bypassing device identification by forging query parameters, but also effectively avoids handling unreasonable or malicious random paths, thereby improving overall security and system stability.


Although adding rate limiting can to some extent alleviate the issue of usage limit exceedance caused by malicious traffic flooding on the worker, when it rains, it pours—if it really exceeds the limit, there's nothing we can do, and the problem of ”background images failing to open after the worker exceeds its limit” must ultimately be faced.

Actually, there is a very simple way to achieve this without modifying the v2.0 code: place an image in the ”root path” (in quotes because for COS, traditional file system concepts like ”root path” and ”directory” are actually just human illusions) of the wallpaper bucket, assuming it is named fallback.jpg (corresponding to the previous access), as shown below:

image.png

Then, you only need to set the background wallpaper image link to ”https://wallpaper.tangwudi.com/fallback.jpg“ (which happens to correspond to the worker's route), for example, my argon theme page background settings:

image.png

Why does setting it this way solve the problem? Because the entry route to the worker was previously set towallpaper.tangwudi.com/fallback.jpg, so setting this address allows it to be processed by the worker. Once the worker strikes because its usage limit is exceeded, and it just so happens that the failure mode of the R2 wallpaper bucket is set to ”automatically open on failure (continue)”:

image.png

Then, access to the background image changes from the image URL returned by the worker to directly accessing the following link: ”https://wallpaper.tangwudi.com/fallback.jpg“, and since this image address actually exists, it achieves background image redundancy after the worker usage limit is exceeded.

Note: It is important to note that although disaster recovery for the background image after worker usage limit exceedance is achieved, if the limit is exceeded due to a DDoS attack, the subsequent attack traffic will hit R2 directly. Therefore, the key is still to properly configure DDoS protection settings on Cloudflare. Friends who are unfamiliar with this can refer to the article:Home Data Center Series Cloudflare Tutorial (Part 5): Introduction to DDoS Attacks and CF DDoS Protection Configuration Tutorial。

Better error handling, enhanced log output

v1.0 version codeWhen handling errors, it only did simple logging and returned a 500 error,Ultimate version code's error handling is more comprehensive and detailed:

console.error(' Error in Worker:', error); // 记录详细错误日志
return new Response('Internal Server Error', { status: 500 });

• Problem Solved: In v2.0, errors are not only caught but also logged in detail, making it easier to debug and troubleshoot. Although it still returns a 500 error, it provides more detailed error output.

v1.0 version codehas very little log output, making it difficult to obtain detailed access information during debugging,v2.0 version codehas added log output:

console.log("User-Agent: ", userAgent); // 记录日志,方便调试
console.log("Selected Folder: ", folder); // 记录日志,方便调试

• Problem Solved: Added logging for User-Agent and the selected folder, making it easier for developers to track and debug requests and file selection logic.

2.5 Implementation of v3.0 Worker Code (Effectiveness to be verified, not recommended for now)

The v2.0 version is completely sufficient for normal personal bloggers, but I am still quite concerned about the issue of ”accessinghttps://wallpaper.tangwudi.com/fallback.jpgdirectly hitting R2 after the worker's free request limit is exceeded”: if the worker usage limit is exceeded due to a DDoS attack, the attack will hit R2 directly, which doesn't feel right. Therefore, it is best to further optimize the code to avoid this situation. Based on this consideration, the v3.0 code was born:

export default {
  async fetch(request, env, ctx) {
    const bucket = env.WALLPAPER_BUCKET; // 绑定 Cloudflare R2 存储桶
    const url = new URL(request.url);
    const pathname = url.pathname.toLowerCase(); // 获取请求路径(忽略大小写)

    // **屏蔽常见爬虫工具**
    const userAgent = (request.headers.get("User-Agent") || "").toLowerCase();
    const cfRay = request.headers.get("cf-ray") || "unknown";

    const botPatterns = [
      "curl", "wget", "httpie", "python-requests", "scrapy", "postmanruntime",
      "go-http-client", "java/", "libwww-perl", "okhttp", "python-urllib",
      "apache-httpclient", "httpclient", "lwp::simple", "mechanize", "aiohttp",
      "axios", "reqwest", "puppeteer", "headlesschrome", "phantomjs"
    ];

    if (!userAgent.trim()) {
      console.log(`[BLOCKED] Empty User-Agent - cf-ray: {cfRay}`);{userAgent} - cf-ray: {cfRay}`);
      return new Response("Forbidden: Bot Detected", { status: 403 });
    }

    // **确定文件夹(PC / Mobile)**
    let folder = "pc_img/"; // 默认 PC

    if (pathname.endsWith("fallback_mobile.jpg")) {
      folder = "mobile_img/";  // 强制移动端壁纸
    } else if (pathname.endsWith("fallback_pc.jpg")) {
      folder = "pc_img/";  // 强制 PC 端壁纸
    } else {
      // **`fallback.jpg` 需要根据 User-Agent 识别**
      const isMobile = /iphone|ipod|android|blackberry|iemobile|opera mini/.test(userAgent);
      folder = isMobile ? "mobile_img/" : "pc_img/";
    }

    console.log("Requested Path:", pathname);
    console.log("Selected Folder:", folder);

    // **初始化内存缓存(Map)**
    const rateLimitCache = new Map(); // 用于缓存速率限制数据

    // **速率限制(60 秒内最多 10 次请求)**
    const clientIP = request.headers.get("CF-Connecting-IP") || "unknown";
    const rateLimitKey = `rate-limit-{clientIP}`;

    // 尝试从内存缓存获取速率限制数据
    let currentCount = rateLimitCache.get(rateLimitKey);
    
    if (!currentCount) {
      // 如果缓存中没有,尝试从 KV 存储获取
      try {
        currentCount = await env.RATE_LIMIT_KV.get(rateLimitKey);
        if (currentCount) {
          currentCount = parseInt(currentCount);
        } else {
          currentCount = 0;
        }
      } catch (error) {
        console.error("Error fetching rate limit from KV:", error);
        // 如果 KV 读取失败,默认从 0 开始
        currentCount = 0;
      }
    }

    const newCount = currentCount + 1;

    // 如果超出速率限制,返回 429 错误
    if (newCount > 10) {
      return new Response("Too Many Requests", { status: 429 });
    }

    // 更新内存缓存和 KV 存储
    rateLimitCache.set(rateLimitKey, newCount);
    try {
      // 如果 KV 存储存在,则更新 KV 存储中的数据
      await env.RATE_LIMIT_KV.put(rateLimitKey, newCount, { expirationTtl: 60 });
    } catch (error) {
      console.error("Error updating rate limit in KV:", error);
      // 即使 KV 更新失败,也不影响请求继续处理
    }

    // **检查 Worker 使用情况**
    const rateLimitUsageKey = "rate-limit-usage";
    let rateLimitUsage = await env.RATE_LIMIT_KV.get(rateLimitUsageKey);

    if (rateLimitUsage && rateLimitUsage > 90) {
      console.log("Worker usage exceeds 90%, caching fallback.jpg at edge");

      // **计算时间直到当天零点**
      const now = new Date();
      const midnight = new Date(now);
      midnight.setHours(24, 0, 0, 0);
      const ttl = (midnight.getTime() - now.getTime()) / 1000; // TTL until midnight in seconds

      // **将 fallback.jpg 写入边缘缓存**
      const fallbackImageUrl = "https://wallpaper.tangwudi.com/fallback.jpg";
      const fallbackResponse = await fetch(fallbackImageUrl);

      if (!fallbackResponse.ok) {
        console.error("Failed to fetch fallback image for caching");
        return new Response("Error caching fallback image", { status: 500 });
      }

      // 将 fallback.jpg 缓存到边缘,直到午夜
      ctx.waitUntil(
        caches.default.put(fallbackImageUrl, fallbackResponse.clone())
      );
      return fallbackResponse;
    }

    try {
      // **获取 R2 存储桶中的文件列表**
      const objects = await bucket.list({ prefix: folder });
      const items = objects.objects;

      if (items.length === 0) {
        return new Response("No images found", { status: 404 });
      }

      // **随机选择一张图片**
      const randomItem = items[Math.floor(Math.random() * items.length)];
      if (!randomItem) {
        return new Response("No valid images", { status: 404 });
      }

      const imageUrl = `https://wallpaper.tangwudi.com/${randomItem.key}`;

      // **设置缓存策略**
      const headers = new Headers();
      headers.set("Cache-Control", "public, max-age=600"); // 浏览器缓存 10 分钟
      headers.set("CDN-Cache-Control", "public, max-age=604800"); // Cloudflare CDN 缓存 7 天
      headers.set("ETag", randomItem.etag);

      // **302 重定向到图片 URL**
      return Response.redirect(imageUrl, 302);
    } catch (error) {
      console.error("Error in Worker:", error);
      return new Response("Internal Server Error", { status: 500 });
    }
  }
};

In this code, the main addition is the query for the worker's free quota: if it exceeds 90%, fallback.jpg will be cached in Cloudflare's CDN (the cache duration is determined by the time difference between the current time and midnight, because the worker's free quota resets at midnight, so we just need to hold out until that point~):

    // **检查 Worker 使用情况**
    const rateLimitUsageKey = "rate-limit-usage";
    let rateLimitUsage = await env.RATE_LIMIT_KV.get(rateLimitUsageKey);

    if (rateLimitUsage && rateLimitUsage > 90) {
      console.log("Worker usage exceeds 90%, caching fallback.jpg at edge");

      // **计算时间直到当天零点**
      const now = new Date();
      const midnight = new Date(now);
      midnight.setHours(24, 0, 0, 0);
      const ttl = (midnight.getTime() - now.getTime()) / 1000; // TTL until midnight in seconds

      // **将 fallback.jpg 写入边缘缓存**
      const fallbackImageUrl = "https://wallpaper.tangwudi.com/fallback.jpg";
      const fallbackResponse = await fetch(fallbackImageUrl);

      if (!fallbackResponse.ok) {
        console.error("Failed to fetch fallback image for caching");
        return new Response("Error caching fallback image", { status: 500 });
      }

      // 将 fallback.jpg 缓存到边缘,直到午夜
      ctx.waitUntil(
        caches.default.put(fallbackImageUrl, fallbackResponse.clone())
      );
      return fallbackResponse;
    }

As a result, since Cache Rules have a higher priority than workers in Cloudflare's traffic sequence, subsequent access to fallback.jpg will no longer hit the worker, but will hit the Cache Rules instead. Although this temporarily loses the random effect of the images, it protects R2, and once the worker quota resets after midnight, it will be back in action!

Note 1: I haven't had the chance to test whether this code is actually feasible yet, and its effectiveness is unknown. It may also cause additional consumption of the daily free KV quota, so generally speaking, everyone can just use the v2.0 code.

Note 2: In the latest v3.0 code, for normal use, you still only need to point the background image tohttps://wallpaper.tangwudi.com/fallback.jpg, and if you want to force the use of PC wallpapers, you only need to point to/fallback_pc.jpg, and if you want to force the use of mobile wallpapers only, point tofallback_mobile.jpg.

Note 3: If you don't want the worker's free quota to be easily exhausted, it is recommended to set up custom rules in Cloudflare WAF to prevent hotlinking and block direct access to the random image API, thereby ensuring that only access requests containing specific referrers can reach and be processed by the worker.

Note 4: This kind of optimization can go even further, such as rate limiting combined with penalties: how long to block if there are more than 10 visits within 60 seconds; using a worker to convert images to webp/avif and returning the best format based on browser support; custom image rules (e.g., returning sunny images in the morning and night views at night); adding image hotlink protection to return a default placeholder image, and so on. However, these features either have ready-made options that can be directly implemented (various Cloudflare rules, WordPress theme options, etc.), or they are not really attractive to average users. Therefore, there is really no need to make the worker code too complex. After all, implementing complex features consumes the worker's free request quota, and the number of free requests is inherently ”more than enough in normal times, but not enough at all in abnormal times”~. So, updating the code in the article to version 3.0 is quite sufficient.

4 Summary

Building your own random image API depends on each individual's actual environment and preferences, and the specific operational details may vary more or less. For example, if you feel that some wallpapers are actually more suitable for day mode and not suitable for late-night mode, and you don't mind the trouble, you can further divide them into 4 directories: day, night, pc, and mobile:/pc_img_day、/pc_img_night,/mobile_img_day,mobile_img_night, put in the correct images, and then optimize the code to determine whether it is day mode or late-night mode, and whether it is a PC or mobile device based on the specific time, thereby returning the correct image URL. As another example, to use different images every month, you can directly create 12 directories by month, and then create PC and mobile directories within each directory, and so on. Whether it is local storage or cloud storage, whether using index.php or Cloudflare worker, the logic is the same. Once the requirements are determined, you can just ask AI for the code part, which is extremely simple.

Currently, the blog's background image has been set to a worker-based random image API (v3.0 code), and the specific worker data can also be seen on Cloudflare:

image.png

In addition, there was an extra discovery after changing the background image from a fixed address to a random image API. After several tests, the PageSpeed Insights test score reached an all-time high of 80 points, which was a pleasant surprise:

image.png

Note: The most optimized caching strategy should actually bypass the cache for access to fallback.jpg, while the actual images should have their edge TTL and browser TTL set as long as possible for the best access performance. Just two cache rules are enough. Try not to set up caching functions in the worker code; after all, the simpler the code, the better.

📌 Content Structure Prompt:
This content belongs to the "Cloudflare Learning Map" part, you can view the complete content path from here: Cloudflare Learning Map 。
View Related Categories · 3 Matches
📎 Related Articles
Share this article
The blog content is original, please indicate the source when reposting! The RSS address of the blog is:https://blog.tangwudi.com/feed, welcome to subscribe; if needed, you can join theTelegram Groupto discuss questions together.

Comments

  1. Xiaoyi
    Android Chrome 139.0.0.0
    1 year ago
    2025-8-26 14:51:31

    Just wondering, is it possible to implement this using worker and OneDrive? After all, OneDrive doesn't seem to have request limits.

    • Owner
      Xiaoyi
      Macintosh Chrome 139.0.0.0
      1 year ago
      2025-8-26 15:11:02

      OneDrive can theoretically be used as well, but it's definitely not as seamless as R2. Worker natively supports R2 (both are Cloudflare products after all) and can be directly bound and read. OneDrive, on the other hand, needs to go through the Microsoft Graph API, which requires handling OAuth authentication, token refreshing, and caching the file list yourself. While a random image API is theoretically possible, its complexity and performance are not as good as R2. Therefore, I highly recommend using R2; it's free, and the 10G free space is generally more than enough.

  2. Very thoughtful, I've learned a lot. Since I mostly use it for personal use (replacing background images for some personal web page styles), I use Weibo image hosting to store images, and use CF worker to implement random images and reverse proxy to modify the Referer to bypass hotlink protection. I didn't set up rate limiting or image caching in CDN either.

    • Owner
      ", it is recommended to disable the automatic plugin update feature on nodes other than the primary node. This is because after the primary node automatically upgrades and updates the plugins, the other nodes can actually upgrade the plugins through Syncthing's synchronization feature, although this synchronization might not be immediately perceived by local plugins (restarting WordPress on the other nodes will allow them to recognize it normally).
      Macintosh Chrome 132.0.0.0
      2 years ago
      2025-2-17 13:42:24

      Actually, features like rate limiting and CDN image caching are not mandatory; they can be implemented directly on Cloudflare. The simpler the worker code, the better. After my v3.0 worker code went live, I even received an email notification that KV usage exceeded 50%~~.

Send Comment Edit Comment


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗ
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
Kaomoji
Emoji
Little Dinosaur
Flower!
Previous
Next
       

👋 Welcome to “Wudi's Personal Blog”

Here, long-term exploration is mainly carried out around the following directions:

🧱 Personal Digital Infrastructure and Blog System Construction
☁️ Cloudflare and Network Architecture Practice
🧠 AI and Knowledge System Exploration
🛡️ Network Security and Access Optimization
🎵 Music and Sound Cognition
👁️ Cognitive Perspectives and Worldviews