After deployment, the site occasionally showed a blank screen on the homepage — refreshing fixed it.
Debugging this took half a day. The culprit was a subtle interaction between Next.js and Cloudflare.
The Problem
After deploying to Cloudflare Pages, most visits work fine. But occasionally — especially when navigating to the homepage from another page — a blank screen appears. Refresh and it's fine.
Sentry reports no errors. The Network tab looks fine — at first glance.
Debugging Process
Step 1: Check Cloudflare Cache
First instinct: Cloudflare cached an intermediate state. Cleared cache. Problem persisted.
Step 2: Browser DevTools
Opened Network tab, enabled "Preserve log", reproduced the issue.
When the blank screen appeared, the / request had Content-Type: text/plain in the response headers, with an empty body.
Normally it should be Content-Type: text/html with index.html content.
Step 3: Content Negotiation
Why does the same URL sometimes return HTML and sometimes text/plain?
Key clue: Before navigating to /, the browser sends a prefetch request:
GET / HTTP/1.1
Accept: text/plain
Accept-Encoding: gzip, deflate, br
This prefetch with Accept: text/plain is from Next.js 15's content negotiation prefetch mechanism.
Root Cause Analysis
Next.js 15 Prefetch Behavior
Before client-side navigation, Next.js 15 prefetches the target resource. For efficiency, it sometimes requests with Accept: text/plain — expecting lightweight metadata instead of full HTML.
Next.js Static Export Creates index.txt
During output: 'export' build, Next.js creates out/index.html for the / route. But internal processes (content negotiation support) also generate out/index.txt.
Cloudflare Pages File Matching
Cloudflare Pages static hosting routing rules:
-
GET /+Accept: text/html→ matchesindex.html✅ -
GET /+Accept: text/plain→ matchesindex.txt✅ (if it exists)
When index.txt exists, Cloudflare Pages does content negotiation based on the Accept header and returns index.txt.
If index.txt is empty, users see a blank page.
The Complete Bug Chain
App Router Link prefetch
→ browser sends GET / Accept: text/plain
→ Cloudflare Pages finds index.txt
→ returns empty text/plain response
→ browser uses this as page content
→ Blank screen!
Evolution: From Blank Screen to 404 Flood
The delete approach fixed the blank screen, but introduced a new problem.
The Problem
With 150+ tool pages, each page triggers an Accept: text/plain prefetch request during client-side navigation. With index.txt deleted, every request returns 404.
F12 Network panel is filled with 404s:
/ 404 (text/plain)
/tools/json-formatter/ 404 (text/plain)
/tools/base64/ 404 (text/plain)
/tools/uuid-generator/ 404 (text/plain)
... (100+)
Impact
- Visual noise: Network panel full of red 404s during debugging
- Wasted bandwidth: Each 404 costs a Cloudflare request and response
- Connection contention: HTTP/1.1 has 6 concurrent connections per domain; 404s steal slots from real resources
- Cloudflare doesn't cache 404s: Every visit hits the origin
Better Approach: Create Empty index.txt
Instead of deleting, create them — let the prefetch hit a valid 200 response:
// scripts/create-index-txt.js
const fs = require('fs')
const path = require('path')
function createIndexTxtFiles(dir) {
let count = 0
function walk(currentDir) {
const indexPath = path.join(currentDir, 'index.html')
if (fs.existsSync(indexPath)) {
const txtPath = path.join(currentDir, 'index.txt')
if (!fs.existsSync(txtPath)) {
fs.writeFileSync(txtPath, '')
count++
}
}
try {
const entries = fs.readdirSync(currentDir, { withFileTypes: true })
for (const entry of entries) {
if (entry.isDirectory()) {
walk(path.join(currentDir, entry.name))
}
}
} catch (e) {}
}
walk(dir)
console.log(`Created ${count} index.txt files`)
}
createIndexTxtFiles(path.join(__dirname, '..', 'out'))
Update build script:
{
"scripts": {
"build": "next build && node scripts/create-index-txt.js"
}
}
Why Creating Is Better Than Deleting
| Aspect | Delete index.txt | Create empty index.txt |
|---|---|---|
| Prefetch result | 404 | 200 OK (0 bytes) |
| Cloudflare cache | ❌ Doesn't cache 404 | ✅ Caches 200 |
| Connection contention | Every request hits origin | Cache HIT, instant return |
| Network panel | Full of red | All green |
| Blank screen risk | None | None (empty file won't override HTML) |
| File size | 0 | 0 bytes × page count |
Key insight: empty index.txt won't cause a blank screen. When Next.js client receives empty content, it knows this isn't valid HTML and automatically falls back to requesting the full index.html with Accept: text/html. Once Cloudflare caches this 200 response, subsequent prefetches are cache HITs — no wasted connections.
Lessons Learned
- Read your hosting platform's file matching docs — Cloudflare Pages' content negotiation differs from standard static servers
- Browser prefetches are a double-edged sword — They optimize load time but add edge cases
-
Inspect the build output directory —
ls -la out/to check for unexpected files - Blank page → check Network first — Wrong Content-Type is often the root cause
- Don't fight the platform — Instead of deleting files and flooding with 404s, work with the mechanism
Project
Full build config and deployment flow at UtlKit — 150+ free online tools, Next.js 15 static export + Cloudflare Pages zero-cost deployment.
If this helped, leave a ❤️.











