What is the location of your example repository?
No repro repo — reproducing needs a store with the Digital Downloads app, a digital product, a custom primary domain, and a live grant token from an order email. The curl commands under Steps to Reproduce show the behaviour against any such store. Happy to put together a minimal scaffold if that's needed to action this.
Which package or tool is having this issue?
Hydrogen
What version of that package or tool are you using?
@shopify/hydrogen@2026.10.0-preview.1
What version of React Router 7 are you using?
N/A — TanStack Start (@tanstack/react-start), not React Router
Steps to Reproduce
HEYOOO Shopify Peeps!
Using Shopify's own Digital Downloads app, since it produces a URL customers already have in their inbox. Any /a/* path behaves the same.
Env: @shopify/hydrogen@2026.10.0-preview.1, TanStack Start on Cloudflare Workers (workerd via @cloudflare/vite-plugin), storeDomain = <shop>.myshopify.com, custom primary domain.
- Install Digital Downloads, attach a file to a product, place a test order.
- Copy the download link from the order email:
https://<primary-domain>/a/downloads/-/<grant>/<token>
- Confirm Shopify serves it on the primary domain:
curl -s -o /dev/null -w '%{http_code}\n' \
-A 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36' \
-H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
-H 'sec-fetch-dest: document' -H 'sec-fetch-mode: navigate' \
"https://<primary-domain>/a/downloads/-/<grant>/<token>"
# 200, HTML containing <div id="digital-downloads-proxy">
- Run a storefront that calls
handleShopifyRoutes in request middleware, then request the same path:
curl -s -o /dev/null -w '%{http_code}\n' "http://localhost:3000/a/downloads/-/<grant>/<token>"
# 404
Proxying it yourself hits two walls. This is what an interceptor would do, since createProxyInterceptor resolves upstream against storefrontClient.storeUrl:
# storeUrl is *.myshopify.com, and the app proxy 301s back to the primary domain
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' \
"https://<shop>.myshopify.com/a/downloads/-/<grant>/<token>"
# 301 https://<primary-domain>/a/downloads/-/... — a loop, the storefront is that domain
# ?_fd=0 stops the redirect, but a non-browser request still 404s
curl -s -o /dev/null -w '%{http_code}\n' \
"https://<shop>.myshopify.com/a/downloads/-/<grant>/<token>?_fd=0"
# 404, empty body
# ?_fd=0 plus the browser headers from step 3
# 200
The /download subpath is the same: 404 bare, 302 to a 24h-signed storage.googleapis.com URL with the headers present.
Expected Behavior
/a/* proxied to the shop origin by handleShopifyRoutes, with the response passed through as-is — status, body, content-type, content-disposition, location on redirects. Same contract handleWellKnownProxy and handleShopifyApiProxy already provide.
So /a/downloads/-/<grant>/<token> returns Shopify's 200, .../download?download=<file> returns Shopify's 302 to the signed URL, and links already in customers' inboxes survive a cutover.
The *.myshopify.com → primary-domain 301 should be handled inside the interceptor. Callers shouldn't need to know _fd=0 exists.
An interceptor built on the existing createProxyInterceptor, matching /^\/(?:a|apps|tools|community)(?:\/|$)/, would cover it. Opt-in is fine if proxying arbitrary /a/* isn't a safe default — but then it needs documenting, because today there's no support and no warning.
Actual Behavior
- No interceptor matches
/a/*, so handleShopifyRoutes returns null, the path falls through to framework routing, misses, and the handleShopifyRedirects fallback finds no redirect for it. Nothing logs. The first signal is a customer with a dead download link.
The workaround is re-implementing the proxy by hand against two undocumented behaviours (_fd=0, browser-header sniffing). Digital Downloads has no API — no DigitalDownload type in the Admin schema — so that means scraping Shopify's own markup for the title, file name, size and download token.
What is the location of your example repository?
No repro repo — reproducing needs a store with the Digital Downloads app, a digital product, a custom primary domain, and a live grant token from an order email. The curl commands under Steps to Reproduce show the behaviour against any such store. Happy to put together a minimal scaffold if that's needed to action this.
Which package or tool is having this issue?
Hydrogen
What version of that package or tool are you using?
@shopify/hydrogen@2026.10.0-preview.1
What version of React Router 7 are you using?
N/A — TanStack Start (@tanstack/react-start), not React Router
Steps to Reproduce
HEYOOO Shopify Peeps!
Using Shopify's own Digital Downloads app, since it produces a URL customers already have in their inbox. Any
/a/*path behaves the same.Env:
@shopify/hydrogen@2026.10.0-preview.1, TanStack Start on Cloudflare Workers (workerdvia@cloudflare/vite-plugin),storeDomain=<shop>.myshopify.com, custom primary domain.https://<primary-domain>/a/downloads/-/<grant>/<token>handleShopifyRoutesin request middleware, then request the same path:Proxying it yourself hits two walls. This is what an interceptor would do, since
createProxyInterceptorresolves upstream againststorefrontClient.storeUrl:The
/downloadsubpath is the same: 404 bare, 302 to a 24h-signedstorage.googleapis.comURL with the headers present.Expected Behavior
/a/*proxied to the shop origin byhandleShopifyRoutes, with the response passed through as-is — status, body,content-type,content-disposition,locationon redirects. Same contracthandleWellKnownProxyandhandleShopifyApiProxyalready provide.So
/a/downloads/-/<grant>/<token>returns Shopify's 200,.../download?download=<file>returns Shopify's 302 to the signed URL, and links already in customers' inboxes survive a cutover.The
*.myshopify.com→ primary-domain 301 should be handled inside the interceptor. Callers shouldn't need to know_fd=0exists.An interceptor built on the existing
createProxyInterceptor, matching/^\/(?:a|apps|tools|community)(?:\/|$)/, would cover it. Opt-in is fine if proxying arbitrary/a/*isn't a safe default — but then it needs documenting, because today there's no support and no warning.Actual Behavior
/a/*, sohandleShopifyRoutesreturnsnull, the path falls through to framework routing, misses, and thehandleShopifyRedirectsfallback finds no redirect for it. Nothing logs. The first signal is a customer with a dead download link.The workaround is re-implementing the proxy by hand against two undocumented behaviours (
_fd=0, browser-header sniffing). Digital Downloads has no API — noDigitalDownloadtype in the Admin schema — so that means scraping Shopify's own markup for the title, file name, size and download token.