Serve SPAs correctly on Cloudflare Workers

Single Page Applications like React expect the server to always return index.html and let the client-side router take over. On Cloudflare Workers you can do that without custom code as long as wrangler.toml is set up correctly. This post shows the minimal configuration and why two options are essential for SPAs.

The SPA problem on the edge

A deployed SPA usually contains compiled assets plus one index.html. Deep links like /dashboard/settings should load the same index.html and let the client router render the view. Without a rewrite you get 404s because the edge tries to find a physical file dashboard/settings that does not exist.

The two crucial wrangler.toml flags

Cloudflare added built-in SPA handling. Set both options in your wrangler.toml:

 1# wrangler.toml
 2name = "my-react-app"
 3compatibility_date = "2024-11-01" # this is specific to your project
 4
 5[assets]
 6# Forces URLs like /app/ to become /app/ so relative links stay correct
 7html_handling = "force-trailing-slash"
 8
 9# Always return your SPA shell (index.html) for unknown routes
10not_found_handling = "single-page-application"
  • html_handling = "force-trailing-slash" ensures that HTML requests end with a trailing slash. That keeps relative asset links like "./static/js/main.js" valid and avoids mixed path resolution, especially when users paste URLs without the slash.
  • not_found_handling = "single-page-application" tells the Worker runtime to serve your SPA entry point instead of a 404 for any route that is not a real asset. This is the key to make deep links work.

Verify your deployment

  1. Build the React app: npm run build.
  2. Deploy: wrangler deploy.
  3. Open deep links directly (e.g., /settings/profile). If everything is configured, the page loads without 404 and the client router renders the correct view.

Troubleshooting

  • Still getting 404? Check that not_found_handling is set to single-page-application and that the assets section is under the root of wrangler.toml.
  • CSS or JS missing? Trailing slashes might be the issue. Keep html_handling = "force-trailing-slash" so relative paths resolve correctly.
  • Mixed content or wrong base path? Ensure your React build uses relative asset paths (the default for create-react-app) and that your Worker domain matches the homepage setting if you changed it.

Takeaway

Two lines in wrangler.toml are enough to make React (and any SPA) work on Cloudflare Workers without custom rewrites:

  • html_handling = "force-trailing-slash"
  • not_found_handling = "single-page-application"

Set them once, deploy, and your SPA will be correctly served for every deep link.


Comments

Twitter Facebook LinkedIn WhatsApp