Skip to content

Commit 54c48af

Browse files
authored
Revert "Revert "Add Cloudflare Pages middleware to handle trailing slash redirects"" (#1634)
Reverts #1633
1 parent c08fce0 commit 54c48af

2 files changed

Lines changed: 121 additions & 0 deletions

File tree

functions/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Cloudflare Pages Functions
2+
3+
This directory contains Cloudflare Pages Functions that enhance the functionality of the docs site.
4+
5+
## Middleware
6+
7+
### `_middleware.js` - Trailing Slash Redirect Handler
8+
9+
This middleware addresses a known Cloudflare Pages issue where URLs without trailing slashes don't match redirect rules in the `_redirects` file.
10+
11+
**Problem it solves:**
12+
- `/wandb/config` → 404 (doesn't match redirect rule)
13+
- `/wandb/config/` → redirects correctly
14+
15+
**How it works:**
16+
1. Intercepts requests to URLs without trailing slashes
17+
2. Checks if adding a slash would match a redirect rule
18+
3. If yes, redirects to the URL with a trailing slash
19+
4. The browser then follows the actual redirect rule
20+
21+
**Example:**
22+
- User visits: `/wandb/config`
23+
- Middleware redirects to: `/wandb/config/`
24+
- _redirects rule redirects to: `/ref/python/sdk/functions/init/`
25+
26+
This is a temporary solution until the migration off Cloudflare Pages is completed.

functions/_middleware.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* Cloudflare Pages middleware to handle trailing slash redirects
3+
*
4+
* This middleware addresses the known Cloudflare Pages issue where URLs without
5+
* trailing slashes don't match redirect rules that include trailing slashes.
6+
*/
7+
8+
export async function onRequest(context) {
9+
const { request, env, next } = context;
10+
const url = new URL(request.url);
11+
12+
// Only process GET and HEAD requests
13+
if (request.method !== 'GET' && request.method !== 'HEAD') {
14+
return next();
15+
}
16+
17+
// Skip if the path already has a trailing slash
18+
if (url.pathname.endsWith('/')) {
19+
return next();
20+
}
21+
22+
// Skip if the path has a file extension
23+
const hasFileExtension = /\.[a-zA-Z0-9]+$/.test(url.pathname);
24+
if (hasFileExtension) {
25+
return next();
26+
}
27+
28+
// Skip certain system paths
29+
const skipPaths = ['/robots.txt', '/sitemap.xml', '/_redirects', '/favicon.ico'];
30+
if (skipPaths.includes(url.pathname)) {
31+
return next();
32+
}
33+
34+
// Skip paths with dots that might not be file extensions
35+
// but could be version numbers or other valid path segments
36+
// Only skip if the dot is followed by common file extensions
37+
const commonExtensions = /\.(html|htm|css|js|json|xml|txt|pdf|jpg|jpeg|png|gif|svg|ico|woff|woff2|ttf|eot)$/i;
38+
if (commonExtensions.test(url.pathname)) {
39+
return next();
40+
}
41+
42+
// Create URL with trailing slash, preserving query and hash
43+
const urlWithSlash = new URL(request.url);
44+
urlWithSlash.pathname = url.pathname + '/';
45+
46+
// Create a new request with the trailing slash
47+
const requestWithSlash = new Request(urlWithSlash.toString(), {
48+
method: request.method,
49+
headers: request.headers,
50+
redirect: 'manual' // Important: don't follow redirects
51+
});
52+
53+
try {
54+
// Fetch with the trailing slash URL
55+
const response = await env.ASSETS.fetch(requestWithSlash);
56+
57+
// If we get a redirect response (301, 302, etc.), redirect to URL with slash
58+
if (response.status >= 301 && response.status <= 308) {
59+
// The path with trailing slash matches a redirect rule
60+
// Redirect the browser to the URL with slash, which will then trigger the actual redirect
61+
// Preserve query parameters and hash
62+
const redirectUrl = new URL(request.url);
63+
redirectUrl.pathname = url.pathname + '/';
64+
65+
return new Response(null, {
66+
status: 301,
67+
headers: {
68+
'Location': redirectUrl.pathname + redirectUrl.search + redirectUrl.hash,
69+
'Cache-Control': 'public, max-age=3600'
70+
}
71+
});
72+
}
73+
74+
// If the response is successful (200), it means the trailing slash version exists
75+
// In this case, we should also redirect to maintain consistency
76+
if (response.status === 200) {
77+
const redirectUrl = new URL(request.url);
78+
redirectUrl.pathname = url.pathname + '/';
79+
80+
return new Response(null, {
81+
status: 301,
82+
headers: {
83+
'Location': redirectUrl.pathname + redirectUrl.search + redirectUrl.hash,
84+
'Cache-Control': 'public, max-age=3600'
85+
}
86+
});
87+
}
88+
} catch (error) {
89+
// Log error but continue with original request
90+
console.error('Trailing slash middleware error:', error);
91+
}
92+
93+
// No redirect found, continue with the original request
94+
return next();
95+
}

0 commit comments

Comments
 (0)