Server rendering sends HTML before the browser application starts. Hydration attempts to reuse that DOM by matching the client's first component tree to the server's structure. A browser-only branch, direct DOM mutation, invalid HTML nesting, or different initial data can break the match. A server-rendered app also needs request-scoped user data: sharing a mutable singleton or caching private responses across accounts can leak another reviewer's case. Transfer of previously fetched data can prevent an immediate duplicate request after hydration, but it must not include forbidden records or cross account boundaries. The first-render contract covers markup, data, and cache scope together.
Angular SSR Hydration and Private Transfer State
Working case
The server renders a permit queue with 47 authorized cases. The browser reads a local display limit during initial component evaluation and renders 29, so hydration sees different rows. A legacy chart directive adds an extra DOM node before hydration, changing the expected structure. Meanwhile a shared server cache keeps a prior reviewer's selected case under a URL-only key. The repair uses a per-request authorized snapshot, defers the local limit until the browser has attached to the server DOM, and moves chart manipulation to a lifecycle point that cannot alter hydration's expected tree. Private transfer data is either excluded or keyed and cleared within the exact account and request boundary. The first browser view shows the same permitted 47 cases before personalization narrows it.
Implementation boundary
import { Component, afterNextRender, computed, input, signal } from "@angular/core";
type PermitSnapshot = { caseCount: number };
@Component({
selector: "permit-queue-header",
standalone: true,
template: `<p>{{ visibleCount() }} permitted cases</p>`
})
export class PermitQueueHeaderComponent {
readonly snapshot = input.required<PermitSnapshot>();
private readonly savedLimit = signal<number | null>(null);
readonly visibleCount = computed(() => {
const count = this.snapshot().caseCount;
const limit = this.savedLimit();
return limit === null ? count : Math.min(count, limit);
});
constructor() {
afterNextRender(() => {
try {
const stored = window.localStorage.getItem("permit-visible-limit");
if (stored === null) return;
const limit = Number(stored);
if (Number.isInteger(limit) && limit >= 0) this.savedLimit.set(limit);
} catch { /* Retain the authorized server count. */ }
});
}
}Create server-side application state for each request and resolve the viewer's permission before rendering private data. Keep the initial component tree deterministic: identical case snapshot, stable IDs, consistent formatting, and valid HTML structure on server and client. Do not read window, local storage, or viewport state during server evaluation. Apply browser-only preferences after hydration and explain the visible update. Audit direct DOM writes from chart libraries and custom directives; isolate them behind a safe lifecycle or a client-only boundary when required. Inspect the framework's transfer-cache policy for requests with credentials and private response headers. A cache key based only on a path is insufficient for per-user case data. Never solve a private-data mismatch by serializing extra cases to every visitor. Compare the initial HTML, first browser tree, and later personalized tree separately.
Cost and boundaries
Hydration saves DOM reconstruction work but requires agreement that can be harder with browser-dependent widgets. A client-only chart avoids a mismatch but delays that chart until scripts run. An authorized transfer snapshot increases response size and private-data exposure if its scope is wrong; excluding it may cause a repeat fetch. Request-scoped state uses initialization CPU and memory but prevents cross-user leakage. A post-hydration preference update can move rows and focus, so test its effect on usability. Measure server time, HTML bytes, hydration errors, duplicate requests, and layout changes under slow script loading. A quiet console is insufficient if forbidden data entered the transfer payload.
Failure trace
Render two reviewers on the same server worker and inspect both HTML and transferred data for cross-request case IDs. Hydrate with a browser display limit of 29 while the server sent 47; first output must match, then the preference may apply. Disable scripts and verify the server HTML still communicates the permitted queue. Insert an extra DOM node through a chart directive before hydration and confirm the regression check catches it. Use a different browser time zone for a displayed date and check stable first formatting. Return a private response marked noncacheable and ensure no shared transfer cache serves it to another account.
Verification
- Server HTML and initial browser tree use the same permitted snapshot.
- Two server requests never share private case state or transfer data.
- Browser-only code cannot mutate the DOM before hydration matches it.
Practice drill
Make two authorized snapshots for reviewers 47 and 81. Render both concurrently, then search their HTML and boot payloads for the other's private case ID. Compare the server DOM with the first browser DOM under a stored row limit and a different time zone. Delay JavaScript, then activate a browser-only chart and record whether it causes a structural mismatch. Audit one HttpClient transfer-cache path for credentials, response cache policy, and account scope. Decide whether it should be transferred, refetched after hydration, or rendered as a neutral shell.
Decision note
Hydrate the same authorized tree the server sent; private transfer data must stay inside the viewer's request boundary.
Common Mistakes
- Reading local storage during initial server/client render.
- Sharing a URL-keyed private transfer cache across accounts.
- Calling hydration recovery a substitute for fixing a structural mismatch.
Related lessons
Angular Signals and Delivery Boundaries; Angular Signal Sources, Computed Views, and Effect Scope; Angular Component Input, Output, and Row Identity; Angular HttpClient Streams and Stale Detail Requests; React Hydration and Stable First Render; Vue Server Rendering and Hydration Stability.
Apply and check
Build Project: Angular permit operations queue and review Web Development: Angular signals and delivery quiz.
