A React micro frontend was receiving valid API responses, but its date fields kept breaking. Nothing in the React application was converting those values. The cause lived outside its codebase.
The remote ran inside a legacy Angular host. It had its own repository, dependencies, and deployment flow, so the boundary looked clean on paper. The browser runtime told a different story.
The symptom appeared in the wrong application
The API returned ordinary JSON with ISO date strings:
{
"createdAt": "2026-09-16T10:30:00Z"
}
By the time React received the parsed response, the same field was already a JavaScript Date object. Form controls, validation, equality checks, and serialization code expected strings, so failures appeared across features that had no obvious connection.
The network tab looked correct. The React data layer looked correct. The conversion happened before the remote touched the response.
The Angular host had changed JSON.parse globally
A utility in the host had replaced the native JSON.parse implementation. Its wrapper detected ISO-looking strings and converted them into Date instances automatically.
const nativeParse = JSON.parse
JSON.parse = (value, reviver) =>
nativeParse(value, (key, item) => {
if (typeof item === 'string' && looksLikeIsoDate(item)) {
return new Date(item)
}
return reviver ? reviver(key, item) : item
})
That convenience may have reduced repetitive conversion code inside the original Angular application. But it did not stay inside Angular. It changed a native API for every script executing in the page.
A micro frontend is not a separate browser
Module Federation creates deployment boundaries, not runtime isolation. The host and remotes may have separate builds, teams, and release schedules, but they still share:
windowand browser-native APIs- history and the current URL
- cookies, local storage, and session storage
- global event listeners and styles
- the DOM and JavaScript execution environment
When the host mutates one of those shared primitives, every remote inherits the new behavior. The React application did not import the Angular utility. It did not need to. The patched function was already waiting on the global object.
Why the bug was expensive to locate
The failure appeared far away from its cause. A developer debugging the React form naturally inspects field adapters, validation schemas, API clients, and response types. All of those can be correct while the runtime contract underneath them has changed.
TypeScript cannot protect against this. The API interface still says createdAt: string; the compiler has no way to know that another application replaced JSON.parse before the request completed.
The decisive diagnostic was to inspect values immediately after parsing and compare the active function with the browser's expected native implementation. Once the global override was visible, the apparently unrelated failures had one explanation.
The fix is an explicit boundary
Date conversion is not inherently wrong. Applying it invisibly to every JSON payload is the problem. Conversion should happen at an owned boundary where its input and output types are explicit.
function mapCandidateResponse(payload: CandidateResponseDto): Candidate {
return {
...payload,
createdAt: new Date(payload.createdAt),
}
}
An API adapter, schema transform, or domain mapper makes the behavior searchable and testable. Features that want dates opt into dates. Other consumers continue receiving normal JSON values.
If a shared behavior truly must cross applications, it should be exposed as a versioned contract—not installed by mutating a native API.
Intentional globals are different from accidental globals
Some micro-frontend architectures deliberately place a bridge or registry on window. That is still global state, but it can be responsible engineering when the namespace, ownership, methods, events, and compatibility policy are explicit.
Monkey-patching JSON.parse has none of those boundaries. Every library already depends on that function, so every library becomes an unknowing consumer of the customization.
The useful test is not “is this global?” It is “can a consumer discover, version, avoid, and test this contract?”
Guardrails for shared browser runtimes
- Do not replace native browser or JavaScript APIs.
- Keep transformations inside API or domain adapters.
- Treat globals as public contracts with owned namespaces.
- Add integration tests that mount remotes inside the real host.
- Validate unknown runtime data instead of trusting compile-time interfaces.
- Document host-level behavior that can affect every remote.
What I took away from it
Scalability is not only splitting a large product into independently deployed applications. It is preserving predictable contracts between those applications.
The bug looked like a React problem, then an API problem. It was ultimately an Angular-host utility changing the meaning of a native JavaScript operation before the remote handled any data.
The wider the scope of a customization, the more explicit its contract must be.
A local helper is easy to reason about. A global override silently becomes everyone else's dependency—and in a micro-frontend system, “everyone” may include applications and teams that do not even share a repository.