Skip to content
Centriu
Centriu Guardian

Server-to-Client Boundary Automation: A Working Build Still Crashed in Production

Centriu Guardian's own in-app manual page — the built-in help screen explaining what Guardian is and how to use it — previously crashed for every real visitor in production, while passing its own build cleanly and working correctly on every developer's own local machine. The root cause, confirmed directly from the fix's own diff, is a specific and easy-to-miss category of mistake in a framework that splits rendering between a server and a browser: the manual page itself is a Server Component, and it passed an ICON COMPONENT — a plain JavaScript function reference, imported directly from an icon library — as a prop into a separate, `"use client"`-marked component responsible for rendering the page's own tabbed navigation. React Server Components can only carry actual, already-rendered elements across that server-to-client boundary; a function is not serializable that way at all, and code that tries to pass one produces a failure only once that exact prop actually needs to cross the boundary at runtime — which never happens during a build's own static type-checking, and never happened in local development either, because the specific route in question is rendered dynamically rather than pre-rendered ahead of time as part of the build. The fix renders each icon into a finished, already-built element on the server side before handing it down as a prop, and changes the shared interface describing that prop from "a component, referenced by its type" to "a piece of already-rendered content" — a change that does not merely fix this one page, but makes the identical mistake, if repeated anywhere else in the codebase in the future, an error the TypeScript compiler catches immediately, rather than one only a real visitor in production would ever discover.
A function crossed the boundary
Now rendered before it crosses
In-app manual and help documentation screen
A component passed where only content could cross.

Splitting rendering between a server and a browser means splitting what can cross between them

A modern React application built with Server Components runs some of its code exclusively on the server — producing the actual markup a visitor's browser receives — and some of its code exclusively in the browser, for anything that needs to respond to a click, hold its own local state, or otherwise behave interactively. The two halves are not simply two places running the identical kind of code: what can pass from the server half into the client half is deliberately limited to values the framework can actually serialize and hand across that boundary, which fundamentally means data and already-rendered content, never a live function reference. A React component, in source code, is itself just a function — passing `<Component />` as an already-invoked ELEMENT crosses the boundary correctly, because an element is a plain, serializable description of what to render; passing the bare `Component` reference ITSELF, expecting the receiving side to call it later, does not, because a function is not something that description format can carry.

How the underlying problem shows up before you fix it

A Server Component page passes a value described in its own type as a component reference (in this specific case, a shared type meant for exactly that: "any icon component from this library") down into a child component explicitly marked `"use client"`.

The build itself completes successfully, and TypeScript's own type-checking raises no error — a component-reference type genuinely does describe a function, and passing a function value where a function-typed prop is expected is, from the type system's own point of view, completely correct.

The page works correctly in local development for a route that a developer visits directly and interactively, but the identical page, deployed to production, throws or renders incorrectly the first time a real, deployed request actually needs the client component to receive and act on that exact prop.

The specific route affected is rendered dynamically rather than statically pre-rendered as part of the build — meaning the build process itself never actually executes the code path where the problematic prop is constructed and would need to cross the server-to-client boundary, so a defect that is entirely deterministic once triggered can still pass every automated build check with zero indication anything is wrong.

The failure, once it does occur, does not point directly at the actual mistake (a function passed where only serializable content is allowed) — it typically surfaces as a framework-level error about serialization or an unexpected value shape, requiring the person debugging it to already understand the Server-Component-versus-Client-Component distinction to connect the error back to its real cause.

Why a type that looks entirely correct can still describe an unsafe value

A type describing "an icon component" is, in the most literal sense, completely accurate for the value actually being passed — an icon library genuinely does export components as plain function references, and a prop typed to accept exactly that shape will happily accept the exact value the code supplies, with the type checker seeing nothing at all to object to. What that type does not, and structurally cannot, express is a second, orthogonal fact the type system has no native way to track: whether the specific prop crosses a server-to-client serialization boundary somewhere between where it is constructed and where it is ultimately used. A type can be perfectly accurate about WHAT KIND of value something is while saying nothing at all about WHERE that value is later allowed to travel — and it is precisely that second, unstated constraint that a Server-Components architecture depends on, silently, everywhere a prop passes from server-authored code into a client-marked component.

How Centriu Guardian moved the render step to the correct side of the boundary

Centriu Guardian's manual page is defined as a Server Component and constructs a small, fixed list of navigation tabs, each carrying a label, a short hint, and an icon drawn from an external icon library — passing that whole list down as a prop into a separate `ManualTabs` component explicitly marked `"use client"`, since it needs to track which tab is currently selected and respond to clicks. The shared `Tab` interface describing each entry in that list previously typed its `icon` field using the icon library's own exported "any icon component" type — describing the icon by its COMPONENT REFERENCE, the underlying function itself, rather than by anything already rendered. The Server Component constructed each tab's icon value by referencing that function directly (`icon: Info`, for instance, rather than `icon: <Info />`), and the client component, on its own side of the boundary, expected to receive that same function reference and invoke it itself, rendering it as `<tab.icon className="..." />` at the point each tab's own visual indicator needed to appear.

That pattern works flawlessly for any component that lives entirely within a single side of the server/client split — the vast majority of ordinary React code. It fails specifically at the exact point a value constructed in server-authored code needs to be serialized and handed across into client-marked code, because a bare function reference cannot be represented in the data format that crossing requires: only already-rendered elements, and other plain, serializable data, can actually make that trip. The manual page's specific combination — a Server Component authoring the tab list, a Client Component receiving and using it — put the icon value directly on that boundary.

The fix changes what actually gets constructed on the server side, and what the client side is asked to expect. Instead of passing the bare icon component reference down, the manual page now renders each icon fully — `<Info className="h-4 w-4" aria-hidden />` rather than `Info` — producing an actual, already-built ReactNode before it is ever placed into the tab list at all. The shared `Tab` interface's `icon` field is re-typed from the icon library's component-reference type to a plain `ReactNode`, and the client component's own rendering logic changes correspondingly, from calling the icon as if it were still a function (`<tab.icon .../>`) to simply placing the already-rendered node directly into the page as-is, with no further invocation needed. Because a Lucide icon's own stroke and fill both read from `currentColor` by design, wrapping the already-rendered icon in the exact same styled `<span>` used before preserves its active/inactive color state correctly, with no separate styling logic needed on the receiving side.

The deeper value of the fix is not confined to this one page: because the shared interface's OWN declared type changed from "a component" to "already-rendered content," any future code anywhere in Guardian's codebase that tries to repeat the identical original mistake — constructing a `Tab` by passing a bare icon component reference rather than a rendered element — now fails TypeScript's own compile-time type-checking immediately, with an ordinary, specific type error, rather than passing every build check silently and waiting for a real, deployed visitor on this specific route to discover it in production.

What is actually built today

Centriu Guardian's manual page renders each navigation tab's icon into a finished element on the server side, before it is ever placed into the shared tab-list data structure passed down to the client-side tab component.

The shared `Tab` interface types its `icon` field as `ReactNode` — already-rendered content — rather than as a bare component reference, closing the exact category of mistake that caused this specific crash for any future addition to the same interface.

The client-side tab component places each already-rendered icon directly into its markup, rather than treating the prop as a function it still needs to invoke.

The fix required no change to how an icon's own color reflects the active or inactive tab state — Lucide's icons read color from the surrounding element by design, which the fix's own wrapping `<span>` already provided before and after the change.

The specific class of defect this fix closes — a value that is type-correct for WHAT it is, but not for WHERE in a Server/Client-split architecture it is allowed to travel — is now caught at compile time for this exact interface, rather than depending on a route happening to be pre-rendered during the build for the mistake to surface early.

Handing someone a letter of introduction instead of the person themselves (illustrative framing of the actual measured finding)

Before the fix, the server side handed the client side a written INSTRUCTION describing which specific icon to bring and render — the equivalent of a letter of introduction naming a person, sent ahead through a channel that can only carry paper, never carry the person themselves — and the client side, expecting to receive and personally invite the actual named person, had nothing usable to work with once that channel could not deliver a person at all. After the fix, the server side sends the fully prepared, already-present GUEST directly — the icon, already rendered — and the client side simply seats whoever arrives, with no separate step of trying to summon someone from a name on a page.

What changes operationally

Centriu Guardian's in-app manual page renders correctly for every visitor now, closing a gap where a component reference — rather than an already-rendered element — was passed across the server-to-client boundary, undetected by the build because the affected route was never actually exercised during static generation.

When this is not the right fit

This automation covers a specific class of Server-Components architecture mistake — passing a component reference, rather than a rendered element, across a server-to-client boundary — as it actually occurred on Guardian's own manual page. It is a distinct concern from this pillar's separate pages on database-privilege scanning or SQL-pattern-matching gaps, which happen to also mention "Server Component" or "RSC" in passing for unrelated systems and unrelated specific mechanisms.

Typing a prop by its VALUE shape vs. typing it by where it is allowed to travel

Typing a shared prop by the shape of the value it literally holds — "this is a component" — is accurate and unsurprising, and it is exactly what a component-reference type is designed to express. It says nothing at all about a second, independent constraint a Server-Components architecture actually depends on: whether that specific prop needs to survive a server-to-client serialization boundary somewhere in its lifetime. Typing the same field by what it is allowed to carry ACROSS that boundary — "already-rendered content," a plain `ReactNode` — makes the boundary's own real requirement part of the type itself, so a future mistake of the identical shape is caught by the compiler rather than discovered by a real visitor in production.

Related systems

Main system: Centriu Guardian.

What it does NOT do

  • Does not change anything about how the manual page's own content — its tabs, headings, or body text — actually reads for a visitor; the fix is entirely about how one specific prop is constructed and typed internally.
  • Does not affect any OTHER component in Guardian that already passes an icon by rendering it into an element first — only the specific pattern of passing a bare component reference across a server-to-client boundary was affected.
  • Does not claim every possible Server-Components serialization mistake across the entire codebase was individually audited and fixed — this fix closes the one confirmed, real instance and re-types the specific shared interface responsible for it, which prevents a recurrence at that exact interface going forward.
  • Does not require Guardian's build process to pre-render every dynamic route in order to catch this category of mistake going forward — the re-typed interface now catches it at compile time instead, independent of which routes the build happens to statically render.
  • Does not extend this same re-typing pattern automatically to unrelated components elsewhere in Guardian that may pass other kinds of values across the same boundary — this fix is scoped to the specific `Tab` interface confirmed to have carried this exact defect.

Security and governance

Centriu Guardian's in-app manual page now renders correctly for every visitor, with the shared component interface re-typed so the specific mistake that caused this production-only crash is now caught by the compiler rather than reaching a real visitor. This is a reliability fix, not an access-control change; any personal or business data referenced elsewhere in Guardian's own records remains subject to Brazil's LGPD (Law No. 13,709/2018). Full detail on access control lives at /governanca and /iso.

Pricing and contracting

Available by monthly subscription, with tiered plans. Values and terms come from the official pricing table at /precos (Centriu's central source — never restated here).

Frequently asked questions

Why did this pass the build if it was a real bug?

Because the build's own static type-checking sees a component-reference value passed where a component-reference type is expected, and considers that correct — the type system has no separate way to express "but not across a server-to-client boundary." And because the specific route is rendered dynamically rather than pre-rendered during the build, the build process itself never actually executed the exact code path that would have triggered the runtime failure.

Why did it work locally for the developer, then?

Local development commonly exercises a route the developer navigates to directly and interactively, which can behave differently from how the identical route is generated in a full production build — and, more fundamentally, the failure depends on the exact moment a value needs to cross the server-to-client serialization boundary, which does not necessarily happen identically in every execution context.

Is this specific to icons, or could it happen with any component?

The specific instance here involved icon components from an icon library, but the underlying mechanism applies to any bare component reference — any value that is, at its core, a function rather than already-rendered content — passed as a prop from a Server Component into a `"use client"` component.

Does re-typing the shared interface really prevent this from happening again?

For this exact interface, yes — a future attempt to pass a bare component reference into a field now typed as `ReactNode` is a straightforward TypeScript type error, caught the moment the code is written or compiled, not discovered later by a real visitor in production.

What does Centriu Guardian cost?

It is sold by subscription with a published starting price — exact current values are on the central pricing page.

See how Centriu Guardian keeps its own in-app manual reliable

Reach our commercial team directly, or leave your details below — we'll follow up with guidance for your case.

Sources

  1. Centriu Guardian — public product page — Centriu, 2026-07-20 · link(primária)
  2. Centriu Guardian — public factsheet (API, JSON) — Centriu, 2026-07-21 · link
  3. Law No. 13,709/2018 — Brazil’s General Data Protection Law (LGPD) — Presidência da República (Brazil), 2018-08-14 · link

Last material update on .

By · AI-assisted production, with human review

We value your privacy

We use cookies to improve your experience, analyze site usage and support our marketing. You can accept all cookies or manage your preferences. To learn more, see our Cookie Policy.