Commit 89c43b5
authored
Add Cloudflare Pages middleware to handle trailing slash redirects (#1631)
## Problem
Cloudflare Pages has a limitation where:
- `/wandb/config/` → redirects correctly to
`/ref/python/sdk/functions/init/` (via _redirects)
- `/wandb/config` → returns 404 (doesn't match the redirect rule)
Recently, we discovered that this results in a Google Search Console
report with over 1000 404s that never hit the Cloudflare redirects at
all.
## Solution
We've implemented a Cloudflare Pages Function middleware that
automatically adds trailing slashes to URLs before they're processed by
the redirect rules.
### How it works
1. The middleware intercepts all GET/HEAD requests
2. For URLs without trailing slashes (and not file extensions or system
files):
- It checks if adding a slash would match a redirect rule
- If yes, it redirects to the URL with a trailing slash
3. The browser then follows the redirect and hits the actual redirect
rule
### Example flow
1. User visits: `/wandb/config`
2. Middleware redirects to: `/wandb/config/` (301)
3. _redirects rule matches and redirects to:
`/ref/python/sdk/functions/init/` (301)
4. Final destination served: `/ref/python/sdk/functions/init/`
## Implementation
### Files created/modified:
1. **`/functions/_middleware.js`** - The edge function that handles
trailing slash redirects
2. **`/public/_routes.json`** - Configuration to ensure the middleware
runs on all routes
## Tested Scenarios
### ✅ Working correctly:
1. **Redirect rules without trailing slashes**
- `/wandb/config` → `/wandb/config/` → `/ref/python/sdk/functions/init/`
- Preserves query parameters: `/wandb/config?test=param` →
`/wandb/config/?test=param`
2. **Files with extensions (not redirected)**
- `/wandb/config.html` → 404 (no redirect)
- `/style.css` → served as-is
- `/script.js` → served as-is
3. **Paths already with trailing slashes (passed through)**
- `/index/` → processed normally
- `/wandb/config/` → triggers redirect rule directly
4. **System files (not redirected)**
- `/robots.txt` → served as-is
- `/favicon.ico` → served as-is
5. **Directory paths**
- `/guides` → `/guides/` (when directory exists)
6. **Special paths**
- `/` → works correctly (already has slash)
- `/path.with.dots` → processed normally (no common file extension)
### Edge cases handled:
- Query parameters are preserved during redirect
- Hash fragments are preserved during redirect
- Only common file extensions skip the middleware (.html, .css, .js,
etc.)
- Paths with dots but uncommon extensions are processed normally
## Deployment Instructions
The middleware will be automatically deployed to production when this PR
is merged. These files are the key:
- `/functions/_middleware.js` - The middleware code
- `/public/_routes.json` - Routes configuration (optional, but
recommended)
## Testing
This has been tested locally by using Wrangler, since it's not so easy
to test it in the Hugo layer.
The middleware is deployed in this PR's preview build for easy testing.
The Cloudflare build logs in this PR show:
```
13:33:47.656 | Successfully read wrangler.toml file.
-- | --
13:33:47.657 | Found Functions directory at /functions. Uploading.
13:33:47.665 | ⛅️ wrangler 3.101.0
13:33:47.665 | -------------------
13:33:48.705 | ✨ Compiled Worker successfully
```
Test these URLs without the trailing slash, and they should redirect as
described:
From | To
------|-----
https://cloudflare-trailing-slash-ed.docodile.pages.dev/wandb/config |
https://cloudflare-trailing-slash-ed.docodile.pages.dev/ref/python/sdk/functions/init/
https://cloudflare-trailing-slash-ed.docodile.pages.dev/library/init |
https://cloudflare-trailing-slash-ed.docodile.pages.dev/ref/python/sdk/functions/init/
https://cloudflare-trailing-slash-ed.docodile.pages.dev/artifacts/api |
https://cloudflare-trailing-slash-ed.docodile.pages.dev/ref/python/artifact/
https://cloudflare-trailing-slash-ed.docodile.pages.dev/sweeps/quickstart
|
https://cloudflare-trailing-slash-ed.docodile.pages.dev/guides/sweeps/walkthrough/
https://cloudflare-trailing-slash-ed.docodile.pages.dev/frameworks/pytorch
|
https://cloudflare-trailing-slash-ed.docodile.pages.dev/guides/integrations/pytorch/
https://cloudflare-trailing-slash-ed.docodile.pages.dev/library/hyperparameter-sweeps
| https://cloudflare-trailing-slash-ed.docodile.pages.dev/guides/sweeps/
https://cloudflare-trailing-slash-ed.docodile.pages.dev/guides/self-hosted
|
https://cloudflare-trailing-slash-ed.docodile.pages.dev/guides/hosting/hosting-options/self-managed/
https://cloudflare-trailing-slash-ed.docodile.pages.dev/company/company-faq
| https://cloudflare-trailing-slash-ed.docodile.pages.dev/support/
https://cloudflare-trailing-slash-ed.docodile.pages.dev/ref/data_types |
https://cloudflare-trailing-slash-ed.docodile.pages.dev/ref/python/data-types/
https://cloudflare-trailing-slash-ed.docodile.pages.dev/weave/boards |
https://cloudflare-trailing-slash-ed.docodile.pages.dev/guides/app/features/panels/query-panels/
https://cloudflare-trailing-slash-ed.docodile.pages.dev/wandb/config?test=param
|
https://cloudflare-trailing-slash-ed.docodile.pages.dev/ref/python/sdk/functions/init/?test=param
(is a no-op but preserves the query parameter
https://cloudflare-trailing-slash-ed.docodile.pages.dev/ref/release-notes/0.73#v0731
|
https://cloudflare-trailing-slash-ed.docodile.pages.dev/ref/release-notes/0.73/#v0731
(preeserves the anchor)
If you are observing in the browser inspector or the Cloudflare logs,
each test should result in two redirects:
1. 301 redirect adding the trailing slash (example: `/wandb/config` →
`/wandb/config/`)
2. 301 redirect to the final destination (example: `/wandb/config/` →
`/ref/python/sdk/functions/init/`)1 parent 0b8eac7 commit 89c43b5
2 files changed
Lines changed: 121 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
0 commit comments