useRequest()
Access URL params, query strings, and request headers from any component — server or client — with a single universal hook.
Overview
useRequest() is a universal hook that exposes the current request's URL parameters, query string, and headers to any React component — whether it runs on the server (SSR) or in the browser as a client component. On the client it is reactive: values update automatically on SPA navigation without a full page reload.
| Environment | Data source |
|---|---|
| SSR (server) | Request store populated by the SSR pipeline before rendering |
| Client (browser) | __n_data JSON blob + window.location, reactive via locationchange |
Basic usage
Import useRequest from nukejs and destructure the fields you need. It works the same way in server components and "use client" components:
import { useRequest } from 'nukejs'
export default async function BlogPost() {
const { params, query, headers, pathname } = useRequest()
const slug = params.slug as string // /blog/hello-world → 'hello-world'
const lang = (query.lang as string) ?? 'en' // ?lang=fr → 'fr'
const locale = headers['accept-language'] // e.g. 'en-US,en;q=0.9'
const post = await db.getPost(slug, lang)
return (
<article>
<p>Reading in: {lang}</p>
<h1>{post.title}</h1>
<div>{post.body}</div>
</article>
)
}Return value
useRequest() returns a RequestContext object with the following fields:
| Field | Type | Description |
|---|---|---|
| pathname | string | Pathname only, no query string. e.g. '/blog/hello' |
| url | string | Full URL with query string. e.g. '/blog/hello?lang=en' |
| params | Record<string, string | string[]> | Dynamic route segments matched by the file-system router. e.g. { slug: "hello" } |
| query | Record<string, string | string[]> | Parsed query string. Multi-value params become arrays. e.g. { tag: ["a", "b"] } |
| headers | Record<string, string> | Request headers. On the client, sensitive headers are stripped. See security note below. |
Reading URL params
Dynamic path segments defined with [param] in the filename are available in params. Catch-all segments ([...slug]) produce arrays:
import { useRequest } from 'nukejs'
export default async function ProductPage() {
const { params } = useRequest()
const category = params.category as string // 'electronics'
const id = params.id as string // '42'
const product = await db.getProduct(category, id)
return <h1>{product.name}</h1>
}import { useRequest } from 'nukejs'
export default function DocsPage() {
const { params } = useRequest()
// Catch-all params are always arrays
const segments = params.path as string[] // ['core', 'routing']
const breadcrumb = segments.join(' › ') // 'core › routing'
return <nav>{breadcrumb}</nav>
}Reading query strings
Query string parameters are parsed and available in query. Repeated keys become string arrays automatically:
import { useRequest } from 'nukejs'
export default async function SearchPage() {
const { query } = useRequest()
const q = (query.q as string) ?? ''
const tags = Array.isArray(query.tag)
? query.tag // ['react', 'ssr']
: query.tag ? [query.tag] : []
const results = await db.search({ q, tags })
return (
<div>
<h1>Results for "{q}"</h1>
<p>Filtered by: {tags.join(', ')}</p>
<ul>
{results.map(r => <li key={r.id}>{r.title}</li>)}
</ul>
</div>
)
}Reading headers
The full set of request headers is available server-side. On the client, a safe subset is embedded in the page's __n_data blob. See the security note below for which headers are stripped.
import { useRequest } from 'nukejs'
export default function HomePage() {
const { headers } = useRequest()
const lang = headers['accept-language'] ?? 'en' // e.g. 'fr-FR,fr;q=0.9'
const userAgent = headers['user-agent'] ?? ''
const isMobile = /mobile/i.test(userAgent)
return (
<main>
<p>Detected language: {lang.split(',')[0]}</p>
{isMobile && <p>You are on a mobile device.</p>}
</main>
)
}Using in client components
useRequest() works inside "use client" components too. On the client it reads from the __n_data blob and window.location, and stays reactive across SPA navigation:
"use client"
import { useRequest } from 'nukejs'
export default function ActiveFilters() {
// Automatically re-renders when the URL changes (e.g. Link navigation)
const { query, pathname } = useRequest()
const sort = (query.sort as string) ?? 'newest'
const filter = (query.filter as string) ?? 'all'
return (
<div className="filters">
<span>Path: {pathname}</span>
<span>Sort: {sort}</span>
<span>Filter: {filter}</span>
</div>
)
}useRouter() for the freshest pathname On the client, params always reflects the __n_data blob written at the time of the most recent SSR or navigation. For the most up-to-date pathname, use useRouter().path instead.Building custom hooks on top
useRequest() is a great primitive for building your own domain-specific hooks. Here are two practical examples:
useI18n — locale from query or Accept-Language header
import { useRequest } from 'nukejs'
const translations = {
en: { welcome: 'Welcome', goodbye: 'Goodbye' },
fr: { welcome: 'Bienvenue', goodbye: 'Au revoir' },
de: { welcome: 'Willkommen', goodbye: 'Auf Wiedersehen' },
} as const
type Locale = keyof typeof translations
function parseLocale(header = ''): Locale {
const tag = header.split(',')[0]?.split('-')[0]?.trim().toLowerCase()
return (tag in translations ? tag : 'en') as Locale
}
export function useI18n() {
const { query, headers } = useRequest()
// ?lang=fr wins over the Accept-Language header
const raw = (query.lang as string) ?? parseLocale(headers['accept-language'])
const locale = (raw in translations ? raw : 'en') as Locale
return { t: translations[locale], locale }
}import { useI18n } from '../../hooks/useI18n'
export default function HomePage() {
const { t, locale } = useI18n()
return (
<main>
<h1>{t.welcome}</h1> {/* 'Bienvenue' for ?lang=fr */}
<p>Active locale: {locale}</p>
</main>
)
}usePagination — page & limit from query string
import { useRequest } from 'nukejs'
export function usePagination(defaultLimit = 20) {
const { query } = useRequest()
const page = Math.max(1, parseInt(query.page as string ?? '1', 10))
const limit = Math.max(1, parseInt(query.limit as string ?? String(defaultLimit), 10))
const skip = (page - 1) * limit
return { page, limit, skip }
}import { usePagination } from '../../hooks/usePagination'
export default async function PostsPage() {
const { page, limit, skip } = usePagination()
const [posts, total] = await Promise.all([
db.getPosts({ skip, limit }),
db.countPosts(),
])
const totalPages = Math.ceil(total / limit)
return (
<div>
<ul>
{posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
<p>Page {page} of {totalPages}</p>
</div>
)
}Security: headers on the client
When NukeJS embeds headers in the __n_data blob, it strips the following sensitive headers before serialising them into the HTML document. This prevents credentials leaking into cached pages or analytics logs:
| Stripped header | Reason |
|---|---|
cookie | Session tokens — never expose to client HTML |
authorization | Bearer tokens and basic auth credentials |
proxy-authorization | Proxy credentials |
set-cookie | Response cookies that should not be re-exposed |
x-api-key | API keys passed as headers |
Server components always receive the full set of headers including cookie and authorization — stripping only applies to what is serialised into the client-facing HTML page.
headers['cookie'] directly from useRequest(). The full string is available on the server and never sent back to the browser via __n_data.import { useRequest } from 'nukejs'
import { parseSessionCookie } from '../../lib/auth'
export default async function DashboardPage() {
// cookie is available server-side only — never exposed to the client
const { headers } = useRequest()
const session = parseSessionCookie(headers['cookie'] ?? '')
if (!session) {
// Redirect to login
return null
}
const user = await db.getUser(session.userId)
return <h1>Welcome, {user.name}</h1>
}TypeScript: RequestContext
The full type is exported from nukejs for use in your custom hooks and utilities:
import { useRequest } from 'nukejs'
import type { RequestContext } from 'nukejs'
// Use the type directly in your own helpers
function getCountryFromContext(ctx: RequestContext): string {
return (ctx.headers['cf-ipcountry'] ?? ctx.query.country ?? 'US') as string
}
export function useFeatureFlag(flag: string): boolean {
const ctx = useRequest()
const country = getCountryFromContext(ctx)
// Enable feature for specific regions via query param override
if (ctx.query['feature_' + flag] === '1') return true
return FEATURE_FLAGS[flag]?.countries?.includes(country) ?? false
}useRequest() requires no context provider or wrapper component. It reads directly from the NukeJS SSR store on the server and from __n_data + window.location on the client.