Server hooks
useServerRequest — the SSR-only escape hatch for reading cookies, headers, and the URL during render.
The only true "server hook" in Voltro is useServerRequest. It exposes the request snapshot during SSR so layouts + pages can read cookies, headers, and the URL during the render pass.
useServerRequest()
import { useServerRequest } from '@voltro/web'
const req = useServerRequest()
// req is { cookies, headers, url } on the server, null on the clientReturns:
interface ServerRequest {
readonly cookies: Readonly<Record<string, string>>
readonly headers: Readonly<Record<string, string>>
readonly url: string // includes query string
}Or null on the client (after hydration).
What it's for
The classic use case: decode the session cookie during SSR so the layout renders the right subject-aware UI:
// src/pages/layout.tsx
import { useServerRequest } from '@voltro/web'
import { SubjectProvider } from '@voltro/plugin-auth/web'
import { decodeSubjectFromRequest } from './lib/auth'
export default function Layout({ children }) {
const req = useServerRequest()
const subject = decodeSubjectFromRequest(req)
return <SubjectProvider subject={subject}>{children}</SubjectProvider>
}decodeSubjectFromRequest is app code (it knows the cookie name + how to verify the signature). The hook just hands you the cookies.
Reading query params
For SSR pages that need to parse the URL's query string:
import { useSearchParams } from '@voltro/web'
export const renderMode = 'ssr' as const
export default function SearchPage() {
// Native `URLSearchParams`, resolved server-side from the SSR request
// URL (correct in the first paint) and from `window.location.search`
// on the client — same call site on both sides.
const q = useSearchParams().get('q') ?? ''
const results = q ? searchDocs(q) : []
return /* … */
}useSearchParams() returns the request's query as a native URLSearchParams. SPA pages
that must react to router-pushed query changes without a reload re-render through the router
(useNavigate/useLocation); the hook then re-resolves on that render.
Reading cookies
const req = useServerRequest()
const sessionCookie = req?.cookies['voltro:session']cookies is already parsed — no manual Cookie: header splitting needed.
Reading headers
const req = useServerRequest()
const userAgent = req?.headers['user-agent']
const tenant = req?.headers['x-tenant']Headers are lower-cased keys. Multi-value headers are joined with , .
Conditional rendering by SSR vs client
const req = useServerRequest()
if (req) {
// SSR — req available
} else {
// Post-hydration — read browser-side equivalents
const ua = navigator.userAgent
}For values that need to flow from SSR to client (cookie-derived subject, locale), serialise them via SubjectProvider-style context. The client doesn't re-read cookies — it inherits what the SSR pass decoded.
What it's NOT for
- Reading database state. Use a
loader— it runs server-side too, but with a typedparams+ abort signal + a clear server/client divide. - Triggering side effects during render. React's render must be pure. For SSR side effects (logging a request), use middleware in
app.config.ts.runtime. - Client-side cookie reading. The session cookie is
HttpOnly— JS can't read it. The server decoded it during SSR + handed you the subject; that's what the client sees.
Mounting check pattern
const req = useServerRequest()
const isSSR = req !== null
return (
<div>
{isSSR ? <ServerOnlyBranch /> : <ClientOnlyBranch />}
</div>
)Use sparingly — divergent SSR / client rendering causes hydration mismatches. The cleaner pattern is useEffect(() => setHydrated(true), []) + render the same JSX in both paths.
See also
- Loaders & meta — server-side data fetch (preferred for data)
- Authentication / React —
SubjectProviderpattern - Wire protocol — the server's view of the request