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.

EnvironmentData 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:

app/pages/blog/[slug].tsxtypescript
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:

FieldTypeDescription
pathnamestringPathname only, no query string. e.g. '/blog/hello'
urlstringFull URL with query string. e.g. '/blog/hello?lang=en'
paramsRecord<string, string | string[]>Dynamic route segments matched by the file-system router. e.g. { slug: "hello" }
queryRecord<string, string | string[]>Parsed query string. Multi-value params become arrays. e.g. { tag: ["a", "b"] }
headersRecord<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:

app/pages/shop/[category]/[id].tsx → /shop/electronics/42typescript
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>
}
app/pages/docs/[...path].tsx → /docs/core/routingtypescript
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:

app/pages/search.tsx → /search?q=nukejs&tag=react&tag=ssrtypescript
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.

app/pages/index.tsxtypescript
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:

app/components/ActiveFilters.tsxtypescript
"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>
    )
}
💡
Prefer 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

hooks/useI18n.tstypescript
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 }
}
app/pages/index.tsxtypescript
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

hooks/usePagination.tstypescript
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 }
}
app/pages/posts.tsx → /posts?page=3&limit=10typescript
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 headerReason
cookieSession tokens — never expose to client HTML
authorizationBearer tokens and basic auth credentials
proxy-authorizationProxy credentials
set-cookieResponse cookies that should not be re-exposed
x-api-keyAPI 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.

ℹ️
Reading cookies server-side To read cookies in a server component, access headers['cookie'] directly from useRequest(). The full string is available on the server and never sent back to the browser via __n_data.
app/pages/dashboard.tsxtypescript
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:

hooks/useFeatureFlag.tstypescript
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
}
Works without any provider Unlike many routing hooks, 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.