Environment
- react-native-windows 0.83.2 (also present on
main as of 2026-07-17), new architecture (Fabric, composition), Win32/HWND host
- Windows 11 (WinINet/WinHTTP cache active)
- Reproduced in a production app against an authenticated JSON API
Steps to reproduce
- Have a backend endpoint that serves
GET responses without a Cache-Control header (very common for JSON APIs — many frameworks send none by default).
- In a react-native-windows app,
fetch() that endpoint (an Authorization header does not prevent the problem).
- Change the data server-side (e.g. save an edit through a
POST/PUT), then fetch() the same GET URL again.
Expected
The second GET hits the network (or at least revalidates) and returns the updated data — matching the behavior of the same JS on React Native Android (OkHttp with no cache configured) and on browsers for heuristically-cacheable authenticated API responses.
Actual
The second GET returns the previous response body with an HTTP 200, with zero network I/O (confirmed: no request reaches the server). The OS HTTP cache (WinINet) answers the request using RFC 7234 heuristic caching, because nothing in the RNW networking stack opts out of cache reads. In our app this manifested as "saves silently revert": a screen re-fetched after a successful save and rendered the pre-save data.
Root cause
RedirectHttpFilter builds its inner filters as default-constructed HttpBaseProtocolFilters and only configures redirect/UI behavior — CacheControl().ReadBehavior is never set, so it stays at HttpCacheReadBehavior::Default, which permits serving cached entries without revalidation:
vnext/Shared/Networking/RedirectHttpFilter.cpp (current main):
- Lines ~72–76:
RedirectHttpFilter(winrt::hstring defaultUserAgent) delegates with winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter{} twice — default filters, default cache behavior.
- Lines ~42–60: the designated constructor sets
AllowAutoRedirect(false) and AllowUI(false) on both inner filters but never touches CacheControl().
- Consumed by
WinRTHttpResource (vnext/Shared/Networking/WinRTHttpResource.cpp), which builds the HttpClient used for all JS fetch/XHR traffic.
The class already implements CacheControl() passthrough (RedirectHttpFilter.cpp ~line 117), so the plumbing exists — nothing ever calls it.
Suggested fix
In the RedirectHttpFilter constructor (next to the AllowAutoRedirect(false) calls), set:
baseFilter.CacheControl().ReadBehavior(winrt::Windows::Web::Http::Filters::HttpCacheReadBehavior::NoCache);
on both inner filters, which aligns RNW with React Native Android (OkHttp is configured with no HTTP cache, so JS-visible fetch semantics match across platforms). If disabling cache reads by default is considered too aggressive, at minimum expose a configuration knob (e.g. via IHttpModuleProxy/HttpSettings or a ReactPropertyBag property) so apps can opt out of WinINet cache reads — today there is no way to do this from app code short of cache-busting every URL.
Workaround (what we ship today)
- Server: middleware adding
Cache-Control: no-store on all API GET responses.
- Client (Windows only): axios/fetch interceptor appending a
_ts=<Date.now()> query parameter to every GET to defeat the WinINet cache key.
Both are required for apps that cannot control every backend; neither should be necessary.
Environment
mainas of 2026-07-17), new architecture (Fabric, composition), Win32/HWND hostSteps to reproduce
GETresponses without aCache-Controlheader (very common for JSON APIs — many frameworks send none by default).fetch()that endpoint (anAuthorizationheader does not prevent the problem).POST/PUT), thenfetch()the sameGETURL again.Expected
The second
GEThits the network (or at least revalidates) and returns the updated data — matching the behavior of the same JS on React Native Android (OkHttp with no cache configured) and on browsers for heuristically-cacheable authenticated API responses.Actual
The second
GETreturns the previous response body with an HTTP 200, with zero network I/O (confirmed: no request reaches the server). The OS HTTP cache (WinINet) answers the request using RFC 7234 heuristic caching, because nothing in the RNW networking stack opts out of cache reads. In our app this manifested as "saves silently revert": a screen re-fetched after a successful save and rendered the pre-save data.Root cause
RedirectHttpFilterbuilds its inner filters as default-constructedHttpBaseProtocolFilters and only configures redirect/UI behavior —CacheControl().ReadBehavioris never set, so it stays atHttpCacheReadBehavior::Default, which permits serving cached entries without revalidation:vnext/Shared/Networking/RedirectHttpFilter.cpp(currentmain):RedirectHttpFilter(winrt::hstring defaultUserAgent)delegates withwinrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter{}twice — default filters, default cache behavior.AllowAutoRedirect(false)andAllowUI(false)on both inner filters but never touchesCacheControl().WinRTHttpResource(vnext/Shared/Networking/WinRTHttpResource.cpp), which builds theHttpClientused for all JSfetch/XHR traffic.The class already implements
CacheControl()passthrough (RedirectHttpFilter.cpp~line 117), so the plumbing exists — nothing ever calls it.Suggested fix
In the
RedirectHttpFilterconstructor (next to theAllowAutoRedirect(false)calls), set:on both inner filters, which aligns RNW with React Native Android (OkHttp is configured with no HTTP cache, so JS-visible
fetchsemantics match across platforms). If disabling cache reads by default is considered too aggressive, at minimum expose a configuration knob (e.g. viaIHttpModuleProxy/HttpSettings or aReactPropertyBagproperty) so apps can opt out of WinINet cache reads — today there is no way to do this from app code short of cache-busting every URL.Workaround (what we ship today)
Cache-Control: no-storeon all API GET responses._ts=<Date.now()>query parameter to every GET to defeat the WinINet cache key.Both are required for apps that cannot control every backend; neither should be necessary.