Moving static files to a CDN or a separate media host is one of the easiest speed wins there is. A common way to do it is a redirect: requests for /assets/... on your site get a 302 to the same path on the media host, and the browser follows it. Images, stylesheets and ordinary scripts carry on working.
When I made exactly that change on a site I run, everything looked fine. The page loaded, the styles were there, images were faster. One feature had quietly stopped working: a search panel that did its work in a Web Worker. No error on screen, just nothing happening.
Why workers break on a redirect
Browsers hold Web Workers to a stricter rule than ordinary scripts. A worker script must come from the same origin as the page, and that includes where it ends up after redirects. If new Worker('/assets/search-worker.js') gets a 302 to another host, the browser refuses to start the worker. An ordinary <script> tag following the same redirect is fine, which is why the rest of the page kept working.
Two related things fail the same way:
- Module workers and their imports. A module worker that imports
./search-index.jsresolves that import against the worker's final URL. Through a redirect, the import base, the origin check and any CORS requirement can all go wrong. - JSON the worker fetches. If the worker loads
/assets/data/index.jsonand that redirects cross-origin, it now needs CORS headers it never needed before.
How to spot it
The browser console tells you, if you look. In Chrome you will see a SecurityError along the lines of "Script at ... cannot be accessed from origin ...". Firefox reports a similar security error when constructing the worker. Nothing appears on the page itself.
From the command line, a bare request without following redirects shows the cause:
curl -sI https://example.com/assets/search-worker.js | head -5
# HTTP/2 302
# location: https://media.example.com/assets/search-worker.js
Run the same check for every file a worker loads: the worker script, each module it imports, and any data it fetches. Any 3xx response pointing at another host is a problem.
A quick way to find them all is to search your code for the places workers are created and the URLs they use:
grep -rnE "new (Shared)?Worker\(|importScripts\(|serviceWorker\.register\(" assets/ src/
Service workers have the same same-origin rule, and a broken one can be harder to notice because the old registration may keep running for a while.
The fix
Keep the files a worker needs on the page's own origin, and let everything else go to the CDN. In practice:
| File | Where it should live |
|---|---|
| Images, fonts, ordinary stylesheets and scripts | Media host or CDN is fine |
| Worker and service worker scripts | The page's own origin |
| Modules imported by a worker | The page's own origin |
| JSON or data fetched by a worker | The page's own origin, or a host with correct CORS headers |
I moved those files to a path the redirect does not cover and updated the references. If you redirect with a web server rule, an exception before the redirect also works. In nginx:
location ^~ /assets/workers/ {
try_files $uri =404;
}
location ^~ /assets/ {
return 302 https://media.example.com$request_uri;
}
Two follow-ups are easy to forget:
- Bump the cache-busting version on the worker URL and its imports (
search-worker.js?v=12, or a new hashed filename). Browsers may otherwise keep using a cached redirect. - Mind long caches on JSON. If your data files are served with a year-long
Cache-Control, moving them without a version change means visitors keep the old copy, or the old redirect, for a very long time. Give data files a versioned name or a shorter cache.
A related gotcha: caching the wrong copy
The other surprise from the same move had nothing to do with workers. I uploaded a new file and requested it through the CDN to check it, about a minute before the publishing step had finished switching the site over. The edge cached what it got at that moment, which was the old response, and kept serving it after the switch.
If you fetch a brand new file through a CDN before your publisher has made it live, the edge may cache the pre-switch copy, including a 404 or a redirect. Either purge that URL after the switch, or wait until publishing has fully finished before you test. Check with a header such as cf-cache-status or age that you are seeing a fresh copy.
A checklist for moving assets
- List every worker, service worker, worklet and module import on the site.
- Exclude those files, and the data they fetch, from the redirect.
- Bump their versions so old cached responses are not reused.
- Test with the console open, and check each worker URL with a bare
curl -I. - Purge the CDN, or wait, before testing new files through it.
Further reading: MDN on the Worker constructor and its same-origin rule.