Cloudflare provides powerful URL manipulation capabilities through Workers, Pages, and Transform Rules. Understanding these features helps you build performant, globally distributed applications.
Key Takeaways
- 1Workers run at edge locations, closest to users
- 2Pages deployments get unique preview URLs
- 3Transform Rules modify URLs without Workers code
- 4URL rewriting happens at the edge before origin
- 5Cache rules can vary by URL parameters
“Cloudflare Workers provides a serverless execution environment that allows you to create new applications or augment existing ones without configuring or maintaining infrastructure.”
Workers URL Routing
Workers run JavaScript at Cloudflare's edge locations, processing requests before they reach your origin server. The URL object in JavaScript provides everything you need to implement routing logic based on paths, query parameters, or any other URL component.
// workers/index.js - URL-based routing
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Route based on path
switch (url.pathname) {
case '/':
return new Response('Home page');
case '/api/users':
return handleUsers(request);
case '/api/health':
return new Response('OK', { status: 200 });
default:
// Check for dynamic routes
if (url.pathname.startsWith('/api/users/')) {
const userId = url.pathname.split('/')[3];
return handleUser(userId);
}
return new Response('Not Found', { status: 404 });
}
}
};
// Query parameter handling
function handleUsers(request) {
const url = new URL(request.url);
const page = parseInt(url.searchParams.get('page')) || 1;
const limit = parseInt(url.searchParams.get('limit')) || 20;
return new Response(JSON.stringify({
page,
limit,
users: []
}), {
headers: { 'Content-Type': 'application/json' }
});
}This Worker demonstrates path-based routing with both static and dynamic routes. The switch handles exact paths while the if statement catches dynamic segments like user IDs. Query parameters are easily extracted using the URL's searchParams interface for pagination and filtering.
Cloudflare Pages gives you automatic preview deployments for every commit and branch. Each deployment gets a unique URL, making it easy to test changes before merging to production.
Cloudflare Pages URLs
| URL Pattern | Description | Example |
|---|---|---|
| projectname.pages.dev | Production | mysite.pages.dev |
| commit.projectname.pages.dev | Commit preview | abc123.mysite.pages.dev |
| branch.projectname.pages.dev | Branch preview | feature.mysite.pages.dev |
| custom.domain.com | Custom domain | www.example.com |
The table shows how Pages generates URLs for different deployment contexts. Branch previews are particularly useful for PR reviews since each branch gets its own stable URL. Custom domains connect your Pages site to your own domain with automatic SSL.
// _redirects file for Pages
/old-path /new-path 301
/blog/* /posts/:splat 301
/api/* https://api.example.com/:splat 200
// _headers file
/api/*
Access-Control-Allow-Origin: *
Cache-Control: no-cache
/*.js
Cache-Control: public, max-age=31536000
// Functions in /functions directory
// /functions/api/hello.js -> /api/hello
export async function onRequest(context) {
const url = new URL(context.request.url);
return new Response(JSON.stringify({
path: url.pathname,
query: Object.fromEntries(url.searchParams)
}), {
headers: { 'Content-Type': 'application/json' }
});
}Pages supports both declarative files (_redirects, _headers) and programmatic Functions. The _redirects file handles common patterns like path mappings and external proxying. Functions in the /functions directory automatically create serverless endpoints matching their file paths.
Transform Rules let you modify URLs through Cloudflare's dashboard without writing Worker code. This is ideal for simple redirects, path rewrites, and query string manipulations that do not require complex logic.
URL Transform Rules
# Transform Rules - modify URLs without code
# Rewrite Rule (invisible to client)
# Incoming: /products/shoes
# Origin sees: /api/v2/products/shoes
Match: URI Path starts with "/products"
Rewrite to: /api/v2$request_uri
# Redirect Rule (visible 301/302)
# Incoming: /old-blog/article
# Redirects to: /blog/article
Match: URI Path starts with "/old-blog"
Redirect to: /blog/${uri.path.removePrefix("/old-blog")}
Status: 301
# Query String Manipulation
# Add tracking parameter
Match: Hostname equals "shop.example.com"
Rewrite to: ${request_uri}${query_string ? "&" : "?"}source=cloudflareTransform Rules use Cloudflare's expression language for matching and template variables for transformations. Rewrites are invisible to the client (the URL in their browser does not change), while redirects send a response that changes the browser's URL.
When Transform Rules are not flexible enough, Workers give you full programmatic control over URL manipulation. You can implement complex routing logic, A/B testing, or conditional redirects based on any request property.
URL Rewriting in Workers
export default {
async fetch(request) {
const url = new URL(request.url);
// Clean URLs - /about -> /about.html
if (!url.pathname.includes('.') && url.pathname !== '/') {
url.pathname = url.pathname + '.html';
}
// API versioning - /api/users -> /api/v2/users
if (url.pathname.startsWith('/api/') && !url.pathname.startsWith('/api/v')) {
url.pathname = '/api/v2' + url.pathname.slice(4);
}
// Remove trailing slashes
if (url.pathname !== '/' && url.pathname.endsWith('/')) {
url.pathname = url.pathname.slice(0, -1);
return Response.redirect(url.toString(), 301);
}
// Lowercase paths
if (url.pathname !== url.pathname.toLowerCase()) {
url.pathname = url.pathname.toLowerCase();
return Response.redirect(url.toString(), 301);
}
// Proxy to origin with modified URL
return fetch(url.toString(), request);
}
};This Worker demonstrates common URL normalization patterns. Clean URLs add .html extensions for static file serving. API versioning injects version prefixes for backward compatibility. The trailing slash and case normalization ensure consistent URLs for SEO and caching.
Cloudflare's cache can be configured to vary by URL patterns, ensuring different paths or query parameters get cached separately. Workers give you fine-grained control over cache keys and TTLs based on URL structure.
Cache by URL
// Workers cache API with URL-based keys
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Cache key variations
const cacheKey = new Request(url.toString(), request);
// Check cache
const cache = caches.default;
let response = await cache.match(cacheKey);
if (!response) {
// Fetch from origin
response = await fetch(request);
// Clone for caching
response = new Response(response.body, response);
// Set cache headers based on URL
if (url.pathname.startsWith('/static/')) {
response.headers.set('Cache-Control', 'public, max-age=31536000');
} else if (url.pathname.startsWith('/api/')) {
response.headers.set('Cache-Control', 'private, max-age=60');
}
// Store in cache
ctx.waitUntil(cache.put(cacheKey, response.clone()));
}
return response;
}
};
// Vary cache by query parameters
function getCacheKey(request) {
const url = new URL(request.url);
// Only include specific params in cache key
const cacheUrl = new URL(url.origin + url.pathname);
['page', 'limit', 'sort'].forEach(param => {
if (url.searchParams.has(param)) {
cacheUrl.searchParams.set(param, url.searchParams.get(param));
}
});
return new Request(cacheUrl.toString());
}The caching code sets different Cache-Control headers based on URL paths. Static assets get long cache times since they rarely change, while API responses get short TTLs for fresher data. The getCacheKey function creates cache keys that only include relevant query parameters, preventing cache fragmentation from irrelevant parameters like tracking codes.
R2 is Cloudflare's object storage service that integrates seamlessly with Workers. You can serve files from R2 through custom URLs, adding authentication, transformations, or access controls at the edge.
R2 Storage URLs
// Serve R2 objects with custom URLs
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Map URL path to R2 key
// /images/photo.jpg -> images/photo.jpg
const key = url.pathname.slice(1);
// Fetch from R2
const object = await env.MY_BUCKET.get(key);
if (!object) {
return new Response('Not Found', { status: 404 });
}
// Return with proper headers
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
'Cache-Control': 'public, max-age=86400',
'ETag': object.httpEtag
}
});
}
};
// R2 public bucket URL
// https://pub-xxx.r2.dev/images/photo.jpg
// R2 with custom domain
// https://assets.example.com/images/photo.jpgThe Worker maps URL paths directly to R2 object keys, serving files with appropriate content types and cache headers. R2 public buckets provide direct URLs, but Workers give you more control over access patterns, content negotiation, and URL structures.