Skip to content

SPA redirect rule <*> intercepts static asset requests despite docs claiming static files take priority #4099

Description

@formsite-uk

Description

The <*>/index.html (200) redirect rule — which Amplify's own "Single page app" wizard inserts — intercepts requests for deployed static files (e.g. /assets/index-[hash].js) and returns index.html with content-type: text/html. CloudFront then caches this HTML response for the asset URL.

This contradicts the documented behaviour:

"Static files in the deployment are always served before custom rules are applied. When a path matches a static file in the deployment, the custom rule is not applied to that path."
Amplify Hosting Redirects and Rewrites docs

Environment

  • Amplify Hosting platform: WEB
  • Region: eu-west-2
  • Deployment type: Amplify Gen 2 (Amplify Console, static SPA)
  • Frontend framework: Vite + Vue 3

Steps to reproduce

  1. Deploy a Vite/Vue (or any SPA) to Amplify Hosting
  2. In the Amplify Console → App settings → Rewrites and redirects, use the "Single page app" preset — this inserts source: <*>, target: /index.html, status: 200
  3. Run:
    curl -sI https://<your-app>.amplifyapp.com/assets/index-[hash].js
    

Expected behaviour

The static file /assets/index-[hash].js is served from S3 with content-type: text/javascript, consistent with the documented static-files-first priority.

Actual behaviour

HTTP/2 200
content-type: text/html
content-length: 1188        ← size of index.html, not the JS bundle
x-cache: Hit from cloudfront
server: AmazonS3

The response body is index.html. The JS file is in the S3 deployment but the <*> rule intercepts the request before S3 is consulted. CloudFront then caches this text/html response against the asset URL with s-maxage=31536000 (1 year).

Impact

The browser throws:

Failed to load module script: Expected a JavaScript-or-Wasm module script
but the server responded with a MIME type of "text/html". Strict MIME type
checking is enforced for module scripts per HTML spec.

The application is completely broken in production for all users. The failure mode is especially hard to diagnose because it returns HTTP 200 with wrong content rather than a 404, and the Amplify Console deployment status shows SUCCEED throughout.

Why this is particularly problematic

Amplify creates the broken rule itself via the "Single page app" wizard. Users following Amplify's own guidance end up with an app that cannot load JavaScript in production, with no warning in the console.

Workaround

Replace <*> with a regex that excludes paths containing known static file extensions:

</^[^.]+$|\.(?!(css|gif|ico|jpg|js|png|txt|svg|woff|ttf|map|json|webp|webmanifest)$)([^.]+$)/>

This can be applied via the AWS CLI:

aws amplify update-app \
  --app-id <your-app-id> \
  --region <your-region> \
  --custom-rules '[{
    "source": "</^[^.]+$|\\.(?!(css|gif|ico|jpg|js|png|txt|svg|woff|ttf|map|json|webp|webmanifest)$)([^.]+$)/>",
    "target": "/index.html",
    "status": "200"
  }]'

Suspected root cause

The rule evaluation does not honour the documented "static files first" priority when the catch-all <*> pattern is used. The pattern matches and short-circuits before the S3 origin is consulted, regardless of whether the requested file exists in the deployment.

Suggested fixes

  1. Fix the implementation so that <*> (and all redirect rules) genuinely evaluate after static file lookup, as documented.
  2. Fix the wizard — either replace the <*> preset with the safe regex, or add a warning that <*> may intercept static asset requests.
  3. Update the docs to clarify the actual evaluation order, or remove the claim that static files always take priority.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions