With Angular's HTTP transfer cache, which requests are skipped by default, and how do withHttpTransferCacheOptions and the transferCache request option change that?
answer
- GET and HEAD only
- auth, cookies, credentials excluded
- Cache-Control and Set-Cookie respected
- global options vs per-request override
basics
~10 sBy default only GET and HEAD are cached, minus requests with auth headers, cookies or credentials and responses marked no-store, no-cache, private or carrying Set-Cookie. withHttpTransferCacheOptions relaxes these globally; transferCache adjusts one request.
solid answer
~30 sThe transfer cache stores `GET` and `HEAD` only, and skips requests with `Authorization`, `Proxy-Authorization` or `Cookie` headers, credentialed requests (`withCredentials`, or Fetch `credentials` of `include` or `same-origin`), anything whose `Cache-Control` says `no-store`, `no-cache` or `private`, and responses with `Set-Cookie`. No response headers are transferred. Globally, `provideClientHydration(withHttpTransferCacheOptions({...}))` accepts `filter` (return `false` to skip a request), `includeHeaders`, `includePostRequests`, `includeRequestsWithAuthHeaders`, `includeRequestsWithCredentials` and `includeNonCacheableRequests`. Per request, `transferCache: false` opts out, `transferCache: { includeHeaders: [...] }` overrides the header list, and a truthy `transferCache` lets a single `POST` in. `withNoHttpTransferCache()` turns the feature off, and it cannot be combined with the options feature.
code
ts · 19 linesimport { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class ArticleApi {
private http = inject(HttpClient);
latestTicker() {
return this.http.get<string[]>('/api/ticker', { transferCache: false });
}
page(n: number) {
return this.http.get<unknown[]>('/api/articles', {
params: { page: n },
observe: 'response',
transferCache: { includeHeaders: ['X-Total-Count'] },
});
}
}go deeper
Remember that only GET and HEAD are cached by default and that authenticated or cookie-carrying requests are left out.
Name each option of withHttpTransferCacheOptions and what it relaxes, and explain what transferCache on a single request can and cannot change.
Treat the auth, credentials and non-cacheable flags as security decisions, and use filter plus per-request opt-outs to keep embedded data minimal.
Decide who may flip the include flags, and tie that decision to how the HTML itself is cached across your delivery path.
## The default eligibility rules Angular's **HTTP transfer cache** replays responses fetched during server-side rendering in the browser. Because the responses end up embedded in HTML that anyone can read, and possibly in a shared cache, the defaults are conservative. In Angular 22.2 a request is stored and reused only when all of these hold: - The method is `GET` or `HEAD`. - The request has no `Authorization`, `Proxy-Authorization` or `Cookie` header. - The request is not credentialed: `withCredentials` is not set and the Fetch `credentials` mode is not `include` or `same-origin`. - Neither the request nor the response has a `Cache-Control` directive of `no-store`, `no-cache` or `private`, and the request's Fetch `cache` mode is not `no-store` or `no-cache`. - The response carries no `Set-Cookie` header. - The global `filter`, if any, does not return `false`, and the request does not say `transferCache: false`. Even for a stored response, **no response headers** are transferred by default. The browser gets the body, status, status text and URL. ## Global options The global options go through `withHttpTransferCacheOptions()`, a feature of `provideClientHydration()` from `@angular/platform-browser`. | Option | Default | What enabling it does | |---|---|---| | `filter(req)` | none | Return `false` to exclude a request; it can only narrow, never widen | | `includeHeaders` | none | Lists response headers copied into the cached entry | | `includePostRequests` | `false` | Caches `POST` too, for read-style APIs such as GraphQL queries | | `includeRequestsWithAuthHeaders` | `false` | Allows requests with auth or cookie headers | | `includeRequestsWithCredentials` | `false` | Allows credentialed requests | | `includeNonCacheableRequests` | `false` | Ignores `Cache-Control` no-store, no-cache and private, and `Set-Cookie` | ```ts import { provideClientHydration, withHttpTransferCacheOptions } from '@angular/platform-browser'; provideClientHydration( withHttpTransferCacheOptions({ includeHeaders: ['ETag'], includePostRequests: true, filter: (req) => !req.url.includes('/api/live-scores'), }), ); ``` ## Per-request control Every `HttpClient` method accepts a `transferCache` option of type `boolean | { includeHeaders?: string[] }`: 1. `transferCache: false` skips the cache for that request, whatever the global settings. 2. `transferCache: { includeHeaders: ['X-Total-Count'] }` replaces the global header list for that request. 3. A truthy `transferCache` on a `POST` lets that single `POST` be cached even when `includePostRequests` is off. The per-request option does **not** override the auth, credentials or `Cache-Control` rules. Those only change through the global flags. ## Turning the whole feature off `provideClientHydration(withNoHttpTransferCache())` keeps hydration and drops the cache. Supplying both `withNoHttpTransferCache()` and `withHttpTransferCacheOptions()` is a contradiction, and Angular throws a configuration error in development mode. ## The missing-header trap A common surprise: code reads `response.headers.get('ETag')` or a pagination header, and during hydration gets `null` because headers were not transferred. In development Angular wraps the replayed headers and logs a warning naming the header and pointing at `includeHeaders`. The fix is to list the header globally or per request. `Cache-Control` is evaluated for eligibility whether or not you include it, so listing it only makes its value readable on the replayed response. ## Worked example: a read-only GraphQL query A page that loads its content with a GraphQL query sends a `POST`, so by default the transfer cache ignores it and the browser repeats the query after hydration. Two ways to fix it: 1. Globally set `includePostRequests: true`. Every `POST` made during the server render becomes eligible, including any that change data. That is acceptable only if the app's server render never issues a mutating `POST`. 2. Per request, pass `transferCache: true` (or `{ includeHeaders: [...] }`) on the query call only. Other `POST` requests stay excluded. The body is part of the cache key, serialized the same way on both sides, so two queries with different variables produce different entries. The second option is usually the safer choice because it names exactly which reads are allowed in. ## Choosing settings - Keep the defaults unless a measured double fetch justifies a change. - Prefer `filter` and per-request `transferCache: false` to shrink what is embedded; they never widen what gets stored. - Treat the three `include*` security flags (`includeRequestsWithAuthHeaders`, `includeRequestsWithCredentials`, `includeNonCacheableRequests`) as security decisions, because they put data into HTML that other layers may cache. - Enable `includePostRequests` only when your `POST` endpoints are pure reads.
- Why can a filter function never make a request cacheable that the defaults exclude?Angular checks `filter` last and only treats a `false` return as a reason to skip. A `true` return does not cancel the method, auth, credentials or Cache-Control checks that ran before it. To admit an excluded class of request you must flip the matching global flag, then use `filter` to narrow that flag back down to the URLs you trust.
- Does the per-request transferCache option let an authenticated GET into the cache?No. The request-level option can opt out, override `includeHeaders` or admit a single `POST`, but the auth-header and credentials exclusions are only lifted by `includeRequestsWithAuthHeaders` and `includeRequestsWithCredentials` in `withHttpTransferCacheOptions()`.
saying these in an interview costs you the question
- Returning true from filter forces an excluded request into the cache.
- Response headers are replayed along with the body by default.
- transferCache: true on a request bypasses the auth-header exclusion.
- POST requests are cached by default because the body is part of the key.
- You can pass withNoHttpTransferCache and withHttpTransferCacheOptions together to reset options.