Skip to content

Infinite loop caused by rule in _redirects #902

Description

@husayt

Describe the bug
when deploying to cloudflare we get this warning.

Parsed 0 valid redirect rules.
2026-06-01T08:49:01.378743Z Found invalid redirect lines:
2026-06-01T08:49:01.378799Z - #1: /* /index.html 200
2026-06-01T08:49:01.378843Z Infinite loop detected in this rule and has been ignored. This will cause a redirect to strip .html or /index and end up triggering this rule again. Please fix or remove this rule to silence this warning.

With ssr: false + preset: "cloudflare_pages", Nitro auto-generates a _redirects file in the build output containing:

/* /index.html 200

This is the standard SPA fallback rule. However, Cloudflare Pages recently added loop detection: since Cloudflare automatically strips .html extensions from URLs, a request to /index.html becomes /index or / → which matches /* again → infinite loop. Cloudflare now rejects this rule entirely, meaning direct navigation to any sub-route on your deployed app will 404.

I am not sure if this to be resolved in Nitro or here, but I wonder if there is better solution than this:

Temporary Fix

Use Cloudflare Pages' native 200.html SPA fallback. When 200.html exists in your output, Cloudflare serves it for all unmatched routes without the loop issue, no redirect rule needed.

Add a hooks section to your nuxt.config.ts:

export default defineNuxtConfig({
  // ...existing config...

  hooks: {
    async 'nitro:build:public-assets'(nitro) {
      const { readFileSync, writeFileSync, copyFileSync, existsSync } = await import('node:fs')
      const { join } = await import('node:path')
      const outDir = nitro.options.output.publicDir
      // Copy index.html → 200.html so Cloudflare uses it as the SPA catch-all
      const indexPath = join(outDir, 'index.html')
      if (existsSync(indexPath)) {
        copyFileSync(indexPath, join(outDir, '200.html'))
      }
      // Remove the now-invalid /* /index.html 200 rule
      const redirectsPath = join(outDir, '_redirects')
      if (existsSync(redirectsPath)) {
        const cleaned = readFileSync(redirectsPath, 'utf-8')
          .split('\n')
          .filter(line => !line.startsWith('/* /index.html'))
          .join('\n')
        writeFileSync(redirectsPath, cleaned)
      }
    },
  },
})

The nitro:build:public-assets hook runs after Nitro compiles and writes its output, so the modifications happen after Nitro's own _redirects is generated, no race condition.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions