API Reference

Complete reference for HadarsOptions, hooks, and TypeScript types.

HadarsOptions

Defined in hadars.config.ts at your project root.

OptionTypeDescription
entrystringPath to your page component. Required.
portnumberHTTP port. Default: 9090.
hmrPortnumberrspack HMR dev server port. Default: port + 1.
baseURLstringPublic base path, e.g. "/app". Default: "".
workersnumberWorker processes forked in run() mode (Node.js only). Default: 1.
proxyRecord / fnPath-prefix proxy rules or a custom async function.
proxyCORSbooleanInject CORS headers on proxied responses.
defineRecordCompile-time constants for rspack's DefinePlugin.
swcPluginsarrayExtra SWC plugins (e.g. Relay compiler).
moduleRulesarrayExtra rspack module rules appended to the built-in set (client + SSR).
fetchfunctionCustom fetch handler. Return a Response to short-circuit SSR.
websocketobjectWebSocket handler (Bun only), forwarded to Bun.serve.
wsPathstringPath that triggers WebSocket upgrade. Default: "/ws".
htmlTemplatestringPath to a custom HTML template with HADARS_HEAD / HADARS_BODY markers.
optimizationobjectOverride rspack optimization for production client builds.
cachefunctionSSR response cache for run() mode. Return { key, ttl? } to cache a request.
pathsfunctionReturns URL list to pre-render with hadars export static. Receives HadarsStaticContext.
sourcesarrayGatsby-compatible source plugins. hadars infers a GraphQL schema from their nodes automatically.
graphqlfunctionCustom GraphQL executor. Passed to paths(), getInitProps(), and useGraphQL().
onErrorfunctionCalled on every SSR render error. Use to forward to Sentry, Datadog, etc. May be async — hadars fires it without awaiting.

Example

import os from 'os';
import * as Sentry from '@sentry/node';
import type { HadarsOptions } from 'hadars';

const config: HadarsOptions = {
    entry: 'src/App.tsx',
    port: 3000,
    workers: os.cpus().length,

    // Proxy /api/* to an internal service
    proxy: { '/api': 'http://localhost:4000' },

    // Short-circuit specific paths
    fetch: async (req) => {
        if (req.pathname === '/health') {
            return new Response('ok');
        }
    },

    // Cache pages by pathname for 60 seconds, skip authenticated users
    cache: (req) => req.cookies.session
        ? null
        : { key: req.pathname, ttl: 60_000 },

    // Expose a build-time constant to client bundles
    define: { '__APP_VERSION__': JSON.stringify('1.2.3') },

    // Forward SSR errors to your monitoring service
    onError: (err, req) => Sentry.captureException(err, {
        extra: { url: req.url, method: req.method },
    }),
};

export default config;

HadarsRequest

The request object passed to getInitProps and the fetch handler:

interface HadarsRequest extends Request {
    pathname: string;               // URL pathname, e.g. "/blog/my-post"
    search: string;                 // Query string, e.g. "?page=2&sort=asc"
    location: string;               // pathname + search
    cookies: Record<string, string>;
}

HadarsApp

The type for your default-exported page component:

type HadarsApp<T> = React.FC<T & {
    location: string;        // current URL (pathname + search)
}>;

// Usage
const App: HadarsApp<Props> = ({ user, location }) => (
    <>
        <HadarsHead status={200}><title>{user.name}</title></HadarsHead>
        ...
    </>
);

Lifecycle hooks

// Runs on the server for every request.
// Return value becomes the props passed to your App component.
export const getInitProps = async (req: HadarsRequest): Promise<Props> => {
    return { user: await db.getUser(req.cookies.session) };
};

// Runs on the server after the final SSR render.
// Strip server-only fields before props are JSON-serialised for the client.
export const getFinalProps = async (props: Props): Promise<PublicProps> => {
    const { dbConnection, ...rest } = props;
    return rest;
};

// Runs in the browser after the page loads.
// Return value replaces/extends props before hydrateRoot is called.
export const getClientProps = async (props: PublicProps): Promise<ClientProps> => {
    return {
        ...props,
        theme: localStorage.getItem('theme') ?? 'dark',
    };
};

useServerData

function useServerData<T>(
    fn: () => Promise<T> | T,
    options?: { cache?: boolean }
): T | undefined

Fetches async data during SSR inside any component. Returns undefined on the first pass(es) while the promise is in-flight, then the resolved value on the final pass. On the client, reads from the hydration cache — fn is never called in the browser. The cache key is derived automatically from the call-site's position in the component tree via useId().

For client-side navigation, fires a single GET <url> with Accept: application/json and suspends via React Suspense until resolved. All useServerData calls within the same render share one request.

Options

OptionTypeDefaultDescription
cachebooleantrueWhen false, the cached value is evicted when the component unmounts so the next mount fetches fresh data from the server. Safe with React Strict Mode.
// cache: false — next mount always fetches fresh data from the server
const uptime = useServerData(() => process.uptime(), { cache: false });

useGraphQL

// Typed — result.data shape is inferred from the DocumentNode
function useGraphQL<TData, TVariables>(
    query: HadarsDocumentNode<TData, TVariables>,
    variables?: TVariables,
): { data?: TData } | undefined

// Untyped fallback for plain query strings
function useGraphQL(
    query: string,
    variables?: Record<string, unknown>,
): { data?: Record<string, unknown> } | undefined

Executes a GraphQL query inside any component using the executor configured via sources or graphql in hadars.config.ts. Integrates with useServerData — the query runs on the server during SSR/export and is hydrated on the client at no extra cost. Returns undefined while pending. GraphQL errors throw, marking the page as failed during static export.

loadModule

// Browser: becomes import('./path') — automatic code splitting
// Server:  becomes Promise.resolve(require('./path')) — statically bundled

const HeavyChart = React.lazy(() => loadModule<{ default: React.FC }>('./HeavyChart'));

Transformed at compile time by the hadars rspack loader. The module is included in the server bundle statically and downloaded on demand in the browser.