@parhelia/react
v1.0.12951
Published
The React bindings of @parhelia/head for a React rendering head on any framework: the editing bridge (ParheliaBridge after hydration, ParheliaBridgeScript on the server), ParheliaSlot for empty slots and the in-place refresh hook (useParheliaRefresh). Nex
Readme
@parhelia/react
The React bindings of @parhelia/head, for a React rendering head on any framework: Next.js (through
@parhelia/next), TanStack Start, React Router, plain Vite SSR. Plain JavaScript, no build step.
| Export | What it is for |
|---|---|
| ParheliaBridge | loads the editing bridge after hydration when the editor frames the head directly |
| ParheliaBridgeScript | renders the bridge's <script> on the server, on the editor's requests |
| ParheliaSlot | marks a slot for the editor, also when it is empty |
| useParheliaRefresh | the in-place refresh as a hook |
| ParheliaPicture | an image cropped by the browser, marked for the editor's cropper |
Everything else a head needs is @parhelia/head's: the server check (@parhelia/head/server), the rendered identity, rich
text, a CMS's markers, the build values and the Vite adapter (@parhelia/head/vite). The constants these components' props
refer to (PARHELIA_BRIDGE_PATH, EMPTY_SLOT_ATTRIBUTE, the bridge's helpers) are re-exported here.
The editing bridge: ParheliaBridge or ParheliaBridgeScript
A head the editor frames directly (its own URL, another origin than the editor's) loads Parhelia's editing bridge itself: a cross-origin frame cannot be given the bridge by its parent. Below Parhelia's rendering proxy the editor injects it, and neither component is needed. One implementation, two ways to load it:
ParheliaBridge, a client component: adds the script after hydration, only in a frame of another origin on?parhelia=1. It needs no request, and the bridge never touches markup React is still hydrating.<ParheliaBridge />ParheliaBridgeScript: renders the<script>where it is rendered, so it is in the server's HTML. Use it where the head's server knows the request and wants the script in the HTML: a CSP nonce,integrity. It renders on every request with?parhelia=1, whatever frames the page (a server cannot see that); in a same-origin frame the editor then injects nothing. Server components can import it (no"use client").// App Router page or layout: the page's searchParams, the response's nonce <ParheliaBridgeScript searchParams={await searchParams} nonce={nonce} /> // a fetch handler or route: the request URL; or decide yourself with `enabled` <ParheliaBridgeScript requestUrl={request.url} />
Both load the bridge from /parhelia/editor-bridge/v1/parhelia-bridge.js on the Parhelia origin (origin, default
parheliaOrigin()) and name that origin as the bridge's parent (data-parhelia-parent-origin): an HTTPS editor framing an
HTTP head sends no referrer to derive the parent from. When another origin serves the bridge (a CMS authoring host that
proxies Parhelia's routes), pass it as bridgeOrigin (environment fallback PARHELIA_BRIDGE_ORIGIN, then
NEXT_PUBLIC_PARHELIA_BRIDGE_ORIGIN): it only serves the script and is never named as the parent. Both take nonce,
integrity, crossOrigin, referrerPolicy and id. The origins come from trusted configuration, never from the query
string or a parent-window message; allow the origin that serves the script in the head's CSP script-src.
shouldRenderParheliaBridgeScript({ searchParams | requestUrl }) is the request check alone (for a non-React render), and
parheliaBridgeScriptProps(options) the script's attributes. @parhelia/editor-bridge-react, which had
ParheliaBridgeScript before, is a deprecated re-export of it.
Empty slots: ParheliaSlot
A Live Preview site tags its fields only, and an empty slot (a reference-array field such as a page's topSection) renders
no field: the editor knows the slot from the page's store but not where it is on the page, so the canvas offers no drop
zone for it. Wrap each slot's rendering; in Parhelia's frames it renders the slot's markers around it and, when the slot is
empty, an empty block (emptyMinHeight, default 6rem) where the editor draws the drop zone; in the editor's preview, which
draws no drop zones, the bridge collapses that block. The blocks inside are still found by their own tags. Outside
Parhelia's frames it renders the children alone.
import { isParheliaFrameRequest } from '@parhelia/head';
import { ParheliaSlot } from '@parhelia/react';
const inParhelia = isParheliaFrameRequest(useRouter().query);
<ParheliaSlot name="topSection" enabled={inParhelia}>
{topSection?.map((entry) => <ComponentResolver key={entry.sys.id} componentProps={entry} />)}
</ParheliaSlot>;name is the slot's key as Parhelia names it: the field's API id for a slot of the page being edited. description (the
label, default name) is optional.
In-place refresh: useParheliaRefresh
The editor calls window.requestRefresh(newUri?) after a save. Mount the hook in Parhelia's editing frames with how the head
reads the page again and a stable string of what it rendered; it reads again once a second until that string changed and
settled, or the deadline passed:
// Next.js Pages Router: getServerSideProps runs again (a fresh query); App Router: router.refresh();
// TanStack Router: router.invalidate()
useParheliaRefresh({ signature: JSON.stringify(pageProps.page), refetch: () => router.replace(withFreshQuery(), undefined, { scroll: false }) });A head that runs another React than the one this package resolves (linked from a checkout) calls
installParheliaRefresh({ refetch, signature: () => latest, deadlineMs }) from @parhelia/head/refresh in its own effect: it
imports no React, and its answer removes the hook again.
Cropped pictures: ParheliaPicture
Renders what contentfulPictureSources (@parhelia/head/contentful) computed: the picture's CSS in a <style>, a clipping box
of the crop's shape, and a <picture> whose sources and image carry their variant (data-variant) and the crop they apply
(data-parhelia-crop). Put the field's annotations on the box (boxProps), so the editor measures the visible box:
import { contentfulPictureSources } from '@parhelia/head/contentful';
import { ParheliaPicture } from '@parhelia/react';
<ParheliaPicture picture={contentfulPictureSources(asset, crops, variants)} alt={asset.description} boxProps={fieldTags} />;A head that runs another React than the one this package resolves renders the same data with its own elements.
