Pass surface and the frame renders exactly one portal area inside your own
page, with no portal header and no portal tabs. Your navigation takes the place
of the portal's tabs: one page per area, each reading as your app's own content.
This is for teams that want their own page structure with none of the UI work:
their layout, our surfaces, iframe isolation.
import { RepostPortal } from "@repost/portal-react";
<RepostPortal url={portalUrl} getPortalToken={getToken} surface="logs" />The four surfaces#
surface | What the customer gets |
|---|---|
"endpoints" | The portal's endpoint area: list, detail and create, including subscriptions and signing secrets. |
"event-catalog" | The event catalog: every subscribable event type, with schemas and sample payloads. |
"logs" | The delivery log: every attempt, newest first, streaming live, with the delivery detail drawer and replay. |
"dead-letter-queue" | The dead letter queue: deliveries that exhausted their retries, with replay. |
Everything the full embed does, a surface does: the
token flow over the private port, theming without reloads, and drawers and
dialogs that cover your viewport rather than being clipped to the frame. The
session comes from the same portal-access call; the props are the same
component's props.
One page per area#
The intended shape is one surface per page, with your own navigation linking them:
/webhooks/endpoints <RepostPortal surface="endpoints" ... />
/webhooks/catalog <RepostPortal surface="event-catalog" ... />
/webhooks/logs <RepostPortal surface="logs" ... />
/webhooks/dlq <RepostPortal surface="dead-letter-queue" ... />The pages differ by nothing but the surface name. Mint one session per
customer server-side and share it across the pages: sessions outlive a
customer's hop between them, so there is no need to pay a platform round-trip
per navigation.
Several surfaces can also share one page. Each is its own frame with its own session, and at most one drawer is open across all of them.
Surfaces stay put#
A bare frame never renders another surface. It moves freely inside its own:
surface="endpoints" still goes from the list to an endpoint and to the create
form. But a navigation that would leave it is suppressed rather than performed.
The customer stays where they are, and the frame tells you where it was headed:
<RepostPortal
url={portalUrl}
getPortalToken={getToken}
surface="logs"
onNavigationIntent={(intent) => router.push(`/webhooks/${intent.surface}`)}
/>intent.surface is one of the four names, so it maps onto your own routes
directly. intent.path is a portal path and stays opaque. Nothing in the portal
links out of a surface today, so the callback is optional: the lock is a
guarantee about what a frame can render, not a workflow you have to handle.
Swapping surface on a mounted component reloads the frame, because it is a
different page. A theme change does not.
Outside React#
createPortalEmbed in @repost/portal-js takes
the same surface and onNavigationIntent options, so Vue, Svelte and vanilla
hosts get the same behavior.
Older portal versions#
A portal deployment that predates surfaces renders the requested area with its chrome, functional, just not bare. Portal deploys precede SDK releases, so in practice a released SDK never meets such a portal.