While using Cloudflare Pages, I encountered a very strange issue: published web pages with the .html extension would automatically trigger a 308 redirect to a URL without the .html suffix. This behavior is hard-coded into the Cloudflare Pages platform and cannot be disabled via configuration settings.
I consulted all the major AI services in Chinese about this problem but found no effective solution. One suggestion was to use .htm instead of .html—since Cloudflare Pages doesn’t rewrite .htm files—but that clearly wasn’t viable for the SEO of an existing website. Another proposed solution involved using Cloudflare Pages Functions as an interceptor: placing a specific code snippet into a functions/[[path]].js file and redeploying. The idea was that this would return a “200 OK” status with the HTML content, bypassing the 308 redirect. I tried this method, but it didn’t work.
Next, I searched Google for keywords like “Cloudflare Pages html 308 Permanent Redirect” and found numerous user reports describing the same issue, yet none offered a working solution; I browsed through several pages of results without finding an answer.
Later, I used Google Translate to translate my original query into English and submitted it to Google Gemini. Gemini provided a completely new solution—one I hadn’t seen before. I tried it out, and it actually worked: running curl -I URL returned a 200 status instead of 308, and accessing the page directly no longer triggered a redirect. It seems English-language AI models are more reliable in this regard.
This solution bypasses the 308 redirect restriction by using Cloudflare Pages Functions (middleware) to intercept the request before the redirect occurs, fetch the HTML internally, and return it to the browser with a “200 OK” status.
Here is the solution for preserving the .html extension:
- Create a folder named
functionsin your project root directory (at the same level as the output/build directory, but not inside it). - Inside that folder, create a file named
_middleware.js. - Add the following code to intercept requests for
.htmlfiles:
The JavaScript code is as follows:
export async function onRequest(context) {
const url = new URL(context.request.url);
// 1. Check if the incoming request is for an .html file
if (url.pathname.endsWith(‘.html’)) {
// 2. Remove the .html extension for the internal lookup
url.pathname = url.pathname.slice(0, -5);
// 3. Clone the original request (to keep headers) but use the new URL
const newRequest = new Request(url.toString(), context.request);
// 4. Fetch the extension-less asset internally.
// This returns the 200 HTML response directly to the browser.
return context.env.ASSETS.fetch(newRequest);
}
// For all other requests, proceed to normal Cloudflare routing
return context.next();
}

