{"id":806,"date":"2025-05-02T11:22:57","date_gmt":"2025-05-02T11:22:57","guid":{"rendered":"https:\/\/www.cmarix.com\/qanda\/?p=806"},"modified":"2026-02-05T12:06:39","modified_gmt":"2026-02-05T12:06:39","slug":"caching-ssr-data-in-angular","status":"publish","type":"post","link":"https:\/\/www.cmarix.com\/qanda\/caching-ssr-data-in-angular\/","title":{"rendered":"What\u2019s the recommended way to cache or reuse SSR data on the Client side after hydration to prevent double API calls?"},"content":{"rendered":"\n<p>To use SSR in Angular or any other frontend framework, the challenge is generally avoiding duplicate API calls on the client side after the hydration. This typically happens because:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The server makes API calls to render the page.<\/li>\n\n\n\n<li>After hydration, the client re-renders the app, often triggering the same API calls again.<\/li>\n<\/ul>\n\n\n\n<p>To prevent double API calls, the recommended approach is to transfer server-fetched data to the client using Angular\u2019s built-in TransferState API.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Recommended Solution: Use TransferState for SSR Data Caching<\/h2>\n\n\n\n<p>Angular provides a utility called TransferState from @angular\/platform-browser, which allows injecting data during SSR and retrieving it on the client without making another API call.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step-by-Step Example<\/h3>\n\n\n\n<p>Let\u2019s walk through a concrete example of how to use TransferState to share API data from SSR to the client.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Service to Fetch Data<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ data.service.ts\nimport { Injectable, inject, PLATFORM_ID } from '@angular\/core';\nimport { HttpClient } from '@angular\/common\/http';\nimport { makeStateKey, TransferState } from '@angular\/platform-browser';\nimport { isPlatformServer } from '@angular\/common';\nimport { Observable, of } from 'rxjs';\nimport { tap } from 'rxjs\/operators';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class DataService {\n  private http = inject(HttpClient);\n  private transferState = inject(TransferState);\n  private platformId = inject(PLATFORM_ID);\n\n  private DATA_KEY = makeStateKey&lt;any>('api-data');\n\n  getData(): Observable&lt;any> {\n    \/\/ If running on the client and state exists, return it\n    if (!isPlatformServer(this.platformId) &amp;&amp; this.transferState.hasKey(this.DATA_KEY)) {\n      const data = this.transferState.get(this.DATA_KEY, null);\n      this.transferState.remove(this.DATA_KEY); \/\/ Clean up after use\n      return of(data);\n    }\n\n    \/\/ Fetch from API\n    return this.http.get('https:\/\/api.example.com\/data').pipe(\n      tap(data => {\n        \/\/ If on the server, save the data to TransferState\n        if (isPlatformServer(this.platformId)) {\n          this.transferState.set(this.DATA_KEY, data);\n        }\n      })\n    );\n  }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Component Using the Service<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app.component.ts\nimport { Component, OnInit } from '@angular\/core';\nimport { DataService } from '.\/data.service';\n\n@Component({\n  selector: 'app-root',\n  template: `&lt;h1>SSR Data&lt;\/h1>&lt;pre>{{ data | json }}&lt;\/pre>`\n})\nexport class AppComponent implements OnInit {\n  data: any;\n\n  constructor(private dataService: DataService) {}\n\n  ngOnInit() {\n    this.dataService.getData().subscribe(res => {\n      this.data = res;\n    });\n  }\n}<\/code><\/pre>\n\n\n\n<p><strong>How It Works:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>During SSR, Angular executes getData(), makes the API call, and stores the result in TransferState.<\/li>\n\n\n\n<li>The rendered HTML includes a &lt;script> tag with serialized data.<\/li>\n\n\n\n<li>When the browser hydrates the page, Angular reads from TransferState instead of re-calling the API.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Why Use TransferState?<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u00a0Prevents duplicate HTTP calls<\/li>\n\n\n\n<li>\u00a0Increases performance by avoiding unnecessary network traffic<\/li>\n\n\n\n<li>\u00a0Improves user experience by displaying the data immediately on load<\/li>\n\n\n\n<li>\u00a0Built into Angular with no external dependencies<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u00a0Best Practices and Tips<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Always use makeStateKey&lt;T>() for strong typing and uniqueness.<\/li>\n\n\n\n<li>Remove data from TransferState after reading it on the client to free memory.<\/li>\n\n\n\n<li>Use platform checks (isPlatformServer, isPlatformBrowser) to conditionally execute logic.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Common Pitfalls<\/h2>\n\n\n\n<p><strong>Not using TransferState results in:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>SSR making an API call\u00a0<\/li>\n\n\n\n<li>Client making the same API call again<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Using Angular\u2019s TransferState API is an effective way to prevent duplicate API calls after SSR hydration. To implement this and other best practices in your projects, you can always <a href=\"https:\/\/www.cmarix.com\/hire-angular-developers.html\">hire Angular developers<\/a> to improve performance and ensure a smoother user experience.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>To use SSR in Angular or any other frontend framework, the challenge is generally avoiding duplicate API calls on the client side after the hydration. This typically happens because: To prevent double API calls, the recommended approach is to transfer server-fetched data to the client using Angular\u2019s built-in TransferState API. Recommended Solution: Use TransferState for [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":938,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[7,3],"tags":[],"class_list":["post-806","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-angular","category-web"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/posts\/806","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/comments?post=806"}],"version-history":[{"count":8,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/posts\/806\/revisions"}],"predecessor-version":[{"id":940,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/posts\/806\/revisions\/940"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/media\/938"}],"wp:attachment":[{"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/media?parent=806"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/categories?post=806"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/tags?post=806"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}