Skip to content

Add Cloudflare Pages middleware to handle trailing slash redirects#1631

Merged
johndmulhausen merged 1 commit into
mainfrom
cloudflare-trailing-slash-edge-function
Sep 11, 2025
Merged

Add Cloudflare Pages middleware to handle trailing slash redirects#1631
johndmulhausen merged 1 commit into
mainfrom
cloudflare-trailing-slash-edge-function

Conversation

@mdlinville

@mdlinville mdlinville commented Sep 11, 2025

Copy link
Copy Markdown
Contributor

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/)

- Created edge function that adds trailing slashes before redirect processing
- Fixes issue where URLs without slashes don't match redirect rules
- Temporary solution until migration off Cloudflare Pages
- Includes documentation and testing instructions
- Preserve query parameter and hashes (anchors) when redirecting
- Detect and handle file extensions
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1cef038
Status: ✅  Deploy successful!
Preview URL: https://7cf51575.docodile.pages.dev
Branch Preview URL: https://cloudflare-trailing-slash-ed.docodile.pages.dev

View logs

@mdlinville mdlinville marked this pull request as ready for review September 11, 2025 20:41
@mdlinville mdlinville requested a review from a team as a code owner September 11, 2025 20:41
@johndmulhausen johndmulhausen merged commit 89c43b5 into main Sep 11, 2025
8 checks passed
@johndmulhausen johndmulhausen deleted the cloudflare-trailing-slash-edge-function branch September 11, 2025 21:07
mdlinville added a commit that referenced this pull request Sep 11, 2025
…rects" (#1633)

Reverts #1631

With this change, docs are down with a Cloudflare error error:

Please check back later
Error 1027
 This website has been temporarily rate limited

You cannot access this site because the owner has reached their plan
limits. Check back later once traffic has gone down.

If you are owner of this website, prevent this from happening again by
upgrading your plan on the [Cloudflare Workers
dashboard](https://dash.cloudflare.com/?account=workers/plans).

[Learn more about this issue
→](https://developers.cloudflare.com/workers/about/limits/#number-of-requests-limit)
mdlinville added a commit that referenced this pull request Sep 11, 2025
Improves on the fix deployed in #1631
so that the function fires only when the URL is missing the trailing
slash and would result in a 404. Should reduce the likelihood of hitting
Cloudflare rate limiting, while slightly increasing the likelihood of
getting an obscure 404 if it is not in one of the top level directories
we are considering.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants