Self use only! You must adjust the code to fit your own needs.
Introduction
Cloudflare is a popular CDN service provider, which can help you to cache your website content and reduce the load on your origin server. However, the default cache settings on Cloudflare may not be the best for your website. In this post, I will show you how to manually implement tiered cache on Cloudflare, which is a good way to improve the cache hit rate and reduce the origin server load.
I use the following three-tier cache strategy:
-
Tier 1: Cloudflare Cache
-
Tier 2: Cloudflare R2 Storage Cache
-
Tier 3: Origin Server Cache
The strategy is very simple:
If the content is not in the Cloudflare cache, it will be fetched from the R2 Storage Cache. If the content is not in the R2 Storage Cache, it will be fetched from the origin server.
I will use above services to implement the tiered cache strategy.
- Cloudflare Cache: Cloudflare’s default cache service.
- Cloudflare R2 Storage Cache: Cloudflare’s R2 Storage Cache service.
- Cloudflare Workers Route
- Origin Server Cache: My origin server’s cache service.
It is important to note that I plan to cache stable static resources only, so I will set some additional rules to bypass dynamic requests.
Preparation
Uplaod Static Resources to R2 Storage Cache
First, I need to upload my static resources to the R2 Storage Cache. I can use the Cloudflare API to upload the resources. I use rclone
to upload the resources to the R2 Storage Cache. Here is an example configuration file and command to upload the resources:
# cat ~/.config/rclone/rclone.conf
[r2]
type = s3
provider = Cloudflare
access_key_id = xxxxxxx
secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
region = auto
endpoint = https://xxxxxx.r2.cloudflarestorage.com
acl = private
no_check_bucket = true
# Upload static resources to R2 Storage Cache
rclone copy /path/to/static/resources/ r2:host.example.com/resources
Set Up Cloudflare Workers Route
-
Create a new Cloudflare Workers.
-
Set up routes for the Cloudflare Workers. I will use the following routes:
host.example.com/resources/*
: Route to the Cloudflare Workers.
-
Bind the Cloudflare R2 Storage Cache to the Cloudflare Workers.
-
Set up the environment variables for the Cloudflare Workers:
CACHE_REGEXP
: Regular expression to match the static resources.
[
"host.example.com/resources/*",
"host.example.com/assets/*",
"host.example.com/static/*"
]
SUFFIX
: The suffix of the static resources.
[
".html",".htm",".css",".js",".js.map.gz",".json",
".png",".jpg",".gif",".svg",".ico",".eot",
".ttf",".woff",".woff2",".otf",".less",".map",".gz"
]
- Disable the default URL in the Cloudflare Workers.
Implementation
export default {
async fetch(request, env, ctx) {
if (!isGetMethod(request)) {
return fetch(request);
}
const cache = caches.default;
// Tier 1: Cloudflare Cache
const cachedResponse = await getCachedResponse(cache, request);
if (cachedResponse) {
return cachedResponse;
}
const { R2, SUFFIX, CACHE_REGEXP } = env;
if (!isValidEnvironment(R2, SUFFIX, CACHE_REGEXP)) {
return fetch(request);
}
const url = new URL(request.url);
if (!matchesSuffix(url.pathname, SUFFIX) || !matchesRegex(url, CACHE_REGEXP)) {
return fetch(request);
}
// Tier 2: Cloudflare R2 Storage Cache
return await fetchFromR2OrFallback(R2, cache, request, url, ctx);
},
};
function isGetMethod(request) {
return request.method === "GET";
}
async function getCachedResponse(cache, request) {
const cachedResponse = await cache.match(request);
if (cachedResponse) {
const headers = new Headers(cachedResponse.headers);
// headers.set("Custom-Cache-Tier", "1");
return new Response(cachedResponse.body, {
headers,
status: cachedResponse.status,
statusText: cachedResponse.statusText,
});
}
return null;
}
function isValidEnvironment(R2, SUFFIX, CACHE_REGEXP) {
return R2 && Array.isArray(SUFFIX) && Array.isArray(CACHE_REGEXP);
}
function matchesSuffix(pathname, suffixes) {
return suffixes.some(suffix => pathname.endsWith(suffix));
}
function matchesRegex(url, regexList) {
const regexPatterns = regexList.map(rule => {
const escapedRule = rule
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
.replace(/\*/g, ".*");
return new RegExp(`^${escapedRule}$`);
});
const fullPath = url.hostname + url.pathname;
return regexPatterns.some(regex => regex.test(fullPath));
}
async function fetchFromR2OrFallback(R2, cache, request, url, ctx) {
const cacheKey = url.hostname + url.pathname;
try {
const r2Object = await R2.get(cacheKey);
if (r2Object) {
const headers = new Headers();
r2Object.writeHttpMetadata(headers);
headers.set("etag", r2Object.httpEtag);
// headers.set("Custom-Cache-Tier", "2");
const response = new Response(r2Object.body, { headers });
ctx.waitUntil(cache.put(request, response.clone()));
return response;
}
} catch (error) {
console.error("Error fetching from R2:", error);
return new Response("Internal Server Error", { status: 500 });
}
// Tier 3: Origin Server Cache
return fetch(request);
}
Conclusion
In this post, I have shown you how to manually implement tiered cache on Cloudflare. By using the tiered cache strategy, you can improve the cache hit rate and reduce the origin server load. I hope this post will help you to optimize your website cache settings on Cloudflare.
Another thing to note is that the Cloudflare Workers needs to be paid for, so you need to consider the cost when implementing the tiered cache strategy.