Client API Reference
All client-side components and hooks are importable from pyxle/client:
import {
Head, Script, Image, ClientOnly,
Form, useAction,
Link, navigate, prefetch, refresh, usePathname
} from 'pyxle/client';Components
<Head>
Manages document <head> elements during server-side rendering.
<Head>
<title>Page Title</title>
<meta name="description" content="Description" />
</Head>Props: Children only (standard React children).
Behaviour:
- Renders
nullin the DOM - During SSR, extracts children markup and registers head elements
- Elements are merged and deduplicated with the
HEADPython variable and layout head blocks - Can be used in any component (page, layout, or nested)
<Script>
Loads external scripts with configurable loading strategies.
<Script src="https://analytics.example.com/script.js" strategy="afterInteractive" />Props:
| Prop | Type | Default | Description |
|---|---|---|---|
src |
string |
(required) | Script URL |
strategy |
string |
"afterInteractive" |
When to load the script |
async |
boolean |
false |
HTML async attribute |
defer |
boolean |
false |
HTML defer attribute |
onLoad |
() => void |
-- | Callback on successful load |
onError |
() => void |
-- | Callback on load failure |
Strategies:
| Value | Description |
|---|---|
"beforeInteractive" |
Injected in <head> before hydration (render-blocking) |
"afterInteractive" |
Loaded after React hydration (default) |
"lazyOnload" |
Loaded during browser idle time |
Behaviour: Renders null. The SSR pipeline extracts <Script> declarations and handles loading according to the strategy.
<Image>
Renders an <img> tag with automatic lazy loading.
<Image src="/hero.jpg" alt="Hero" width={1200} height={630} />Props:
| Prop | Type | Default | Description |
|---|---|---|---|
src |
string |
(required) | Image source URL |
alt |
string |
"" |
Alt text for accessibility |
width |
number |
-- | Image width in pixels |
height |
number |
-- | Image height in pixels |
priority |
boolean |
false |
Eager loading (above-the-fold images) |
lazy |
boolean |
true |
Lazy loading (below-the-fold images) |
Behaviour: Renders a standard <img> with loading="eager" when priority is true, loading="lazy" otherwise. All additional props are forwarded to the <img> element.
<ClientOnly>
Renders children only on the client after hydration.
<ClientOnly fallback={<p>Loading...</p>}>
<MapWidget />
</ClientOnly>Props:
| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
-- | Content to render on the client |
fallback |
ReactNode |
null |
Placeholder shown during SSR |
Behaviour: Returns fallback during SSR and on first client render. After useEffect fires (hydration complete), switches to rendering children. Prevents hydration mismatch for browser-only content.
<Form>
Progressive-enhancement form component for calling server actions.
<Form action="create_post" onSuccess={(data) => alert(`Created: ${data.id}`)}>
<input name="title" required />
<button type="submit">Create</button>
</Form>Props:
| Prop | Type | Default | Description |
|---|---|---|---|
action |
string |
(required) | Name of the @action function |
pagePath |
string |
current page | Page where the action is defined |
onSuccess |
(data) => void |
-- | Called with response data on success |
onError |
(message) => void |
-- | Called with error message on failure |
resetOnSuccess |
boolean |
true |
Reset form fields after success |
children |
ReactNode |
-- | Form contents |
Behaviour:
- With JavaScript: intercepts submit, serialises form data to JSON, POSTs to the action endpoint
- Without JavaScript: falls back to a standard HTML form POST
- Displays inline error messages on failure
- Automatically resolves the action endpoint URL
- All additional props are forwarded to the
<form>element
<Link>
Client-side navigation link that prevents full page reloads.
<Link href="/about">About Us</Link>Imported from pyxle/client. Renders an <a> tag that intercepts clicks for client-side navigation.
Hooks
useAction(actionName, options?)
Hook for calling server actions programmatically.
const deleteItem = useAction('delete_item');
async function handleDelete(id) {
const result = await deleteItem({ id });
if (result.ok) {
console.log('Deleted');
}
}Parameters:
| Parameter | Type | Description |
|---|---|---|
actionName |
string |
Name of the @action function |
options.pagePath |
string? |
Page where the action is defined (defaults to current page) |
options.onMutate |
(payload) => void |
Called immediately before the request (for optimistic updates) |
Returns: An async function with attached state properties.
Return value properties:
| Property | Type | Description |
|---|---|---|
.pending |
boolean |
true while request is in flight |
.error |
string | null |
Error message on failure |
.data |
object | null |
Last successful response data |
Calling the returned function:
const result = await actionFn(payload);
// result.ok: boolean
// result.error?: string
// result.*: response data fieldsBehaviour:
- New calls abort previous in-flight requests
- State resets on each new call
onMutatefires synchronously before the fetch (use for optimistic UI)
usePathname()
Reactive hook that returns the current URL pathname and re-renders on client-side navigation.
import { usePathname, Link } from 'pyxle/client';
function NavLink({ href, children }) {
const pathname = usePathname();
const active = pathname === href;
return (
<Link href={href} className={active ? 'text-emerald-400' : 'text-zinc-400'}>
{children}
</Link>
);
}Returns: string — the current pathname (e.g. /dashboard/settings).
Behaviour:
- Reads
window.location.pathnameon the client - During SSR, returns the path currently being rendered (via
globalThis.__PYXLE_CURRENT_PATHNAME__) so the first client render matches — no hydration mismatch - Subscribes to framework navigation events (
Link,navigate(),refresh(),popstate) and re-renders on change
Functions
navigate(path)
Trigger client-side navigation programmatically.
import { navigate } from 'pyxle/client';
navigate('/dashboard');prefetch(path)
Prefetch a page's data and assets.
import { prefetch } from 'pyxle/client';
<button onMouseEnter={() => prefetch('/dashboard')}>
Go to Dashboard
</button>refresh()
Re-run the current page's @server loader and re-render with fresh data. Does not reload the page or change scroll position.
import { refresh } from 'pyxle/client';
<button onClick={() => refresh()}>
Refresh data
</button>invalidate(url?)
Drop a URL from the client-side navigation cache so the next navigate(url) refetches the loader payload instead of replaying the cached one. Call this after a mutation (create, update, delete) that affects a list view the user might navigate back to.
Without an argument, clears every cached entry. Returns true if an entry was evicted.
import { invalidate, navigate } from 'pyxle/client';
async function handleDelete(id) {
await deletePost({ id });
invalidate('/posts'); // drop the cached /posts list
navigate('/posts'); // next visit refetches
}Related: server-driven invalidation. Your @action can tell the client which URLs to invalidate via the `invalidate_routes()` helper. Responses carrying an x-pyxle-invalidate header are honoured automatically by useAction and <Form>, so most apps never call invalidate() in client code directly.
Navigation cache TTL
Client-side loader payloads are cached per URL for 30 seconds by default so back/forward navigation is instant while data stays reasonably fresh. Tune the cap by setting a global on the window before Pyxle's client runtime boots (e.g. in a <Script strategy="beforeInteractive"> block):
<Script strategy="beforeInteractive">
{`window.__PYXLE_NAV_STALE_MS__ = 60000;`} {/* 60s */}
</Script>Useful values:
0— never cache; every navigation hits the server.30000(default) — matches Next.js App Router's heuristic.- a large number — cache for the lifetime of the tab.
For per-mutation control, prefer `invalidate(url)` or the x-pyxle-invalidate response header over global TTL tuning.