Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.hyperauth.dev/llms.txt

Use this file to discover all available pages before exploring further.

This guide shows you how to wire @hyperauth/react into a Next.js App Router project. By the end, your app will mount HyperAuthProvider on the client, emit the Content Security Policy directives the SDK needs to boot, and read its runtime configuration from environment variables. Use this guide if you build with Next.js 14 or later on the App Router. If you use Vite or another bundler, the Quickstart covers the simpler setup path.

Prerequisites

  • A Next.js 14+ project using the App Router.
  • Node.js 18 or higher and a package manager (npm, pnpm, or bun).
  • A browser that supports WebAuthn (Chrome, Firefox, or Safari).

Install the packages

@hyperauth/react declares viem as a peer dependency. Install all three together:
npm install @hyperauth/client @hyperauth/react viem
Make sure your lockfile resolves a single copy of react. If you ship a workspace with multiple apps, dedupe React at the root — the provider relies on useSyncExternalStore, which throws if it sees two React instances.

Wrap the provider for the client boundary

HyperAuthProvider spawns browser workers and reads useSyncExternalStore at module-eval time. Both require the client runtime, so import it through next/dynamic with ssr: false:
app/_providers/hyperauth-provider.tsx
"use client";

import dynamic from "next/dynamic";
import type { ClientConfig } from "@hyperauth/client";
import type { ReactNode } from "react";

const config: ClientConfig = {
  // Falls back to https://cdn.hyperauth.dev/latest/{enclave,vault}.wasm
  // when the env vars are undefined.
  wasmUrl: process.env.NEXT_PUBLIC_HYPERAUTH_ENCLAVE_WASM_URL,
  vaultWasmUrl: process.env.NEXT_PUBLIC_HYPERAUTH_VAULT_WASM_URL,
};

const HyperAuthProviderClient = dynamic(
  () => import("@hyperauth/react").then((m) => ({ default: m.HyperAuthProvider })),
  { ssr: false },
);

export function DashboardHyperAuthProvider({ children }: { children: ReactNode }) {
  return (
    <HyperAuthProviderClient
      projectId={process.env.NEXT_PUBLIC_HYPERAUTH_PROJECT_ID}
      chain={(process.env.NEXT_PUBLIC_HYPERAUTH_CHAIN ?? "base") as "base"}
      config={config}
    >
      {children}
    </HyperAuthProviderClient>
  );
}
Don’t import @hyperauth/react directly into app/layout.tsx. The package’s production bundle uses react/jsx-runtime, and Next.js will try to resolve the SSR path of the provider at build time. Routing the import through next/dynamic({ ssr: false }) keeps the SDK off the server entirely.
Then mount the wrapper in the root layout:
app/layout.tsx
import { DashboardHyperAuthProvider } from "./_providers/hyperauth-provider";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <DashboardHyperAuthProvider>{children}</DashboardHyperAuthProvider>
      </body>
    </html>
  );
}
Components inside the tree can now call useHyperAuth and the other hooks from @hyperauth/react. See the React Hooks Reference for the full surface.

Emit the required CSP

The SDK needs a specific set of Content Security Policy directives to boot. Without them, workers fail to start, OPFS writes are blocked, or the bundler ALPN handshake silently drops. Use Next.js middleware (named proxy.ts in Next 16+, middleware.ts in earlier versions) to attach the header:
proxy.ts
import { NextResponse, type NextRequest } from "next/server";
import { cspManifestString } from "@hyperauth/react";

// Next.js Server Components, next-themes, and framer-motion inject inline
// <style> tags. The SDK manifest does not include style-src, so we add it
// here. 'unsafe-inline' is the standard App Router workaround.
const APP_CSP = "style-src 'self' 'unsafe-inline'";

const CSP = `default-src 'self'; ${cspManifestString()}; ${APP_CSP}`;

// Next.js dev injects inline scripts (Turbopack HMR + RSC hydration markers)
// that would violate script-src 'self' 'wasm-unsafe-eval'. Those scripts
// don't exist in production builds, so we only enforce CSP in production.
const ENFORCE_CSP = process.env.NODE_ENV === "production";

export function proxy(_request: NextRequest) {
  const response = NextResponse.next();
  if (ENFORCE_CSP) {
    response.headers.set("Content-Security-Policy", CSP);
  }
  return response;
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
If @hyperauth/react’s exports map can’t be resolved from the edge runtime in your setup, inline the directive string verbatim by running bun --print "import('@hyperauth/react').then(m => console.log(m.cspManifestString()))" and pasting the output. Re-run after each SDK upgrade.

Why CSP is gated to production

Next.js dev injects inline scripts for HMR and RSC hydration markers that would violate script-src 'self' 'wasm-unsafe-eval'. Those scripts don’t exist in production builds, so the default gate is NODE_ENV === "production". A nonce-based strategy is required to enforce CSP in dev too — out of scope for this guide.

Configure runtime env vars

Document the SDK’s runtime configuration in .env.example so contributors know what to set:
.env.example
# WASM URLs. Unset → falls back to:
#   https://cdn.hyperauth.dev/latest/enclave.wasm
#   https://cdn.hyperauth.dev/latest/vault.wasm
# Pin to a specific version for production deployments.
NEXT_PUBLIC_HYPERAUTH_ENCLAVE_WASM_URL=
NEXT_PUBLIC_HYPERAUTH_VAULT_WASM_URL=

# UCAN policy token issued by HyperAuth for this app. Unset is fine in
# dev — the SDK will boot but RPC calls will return 401.
NEXT_PUBLIC_HYPERAUTH_PROJECT_ID=

# Default chain for embedded wallets. Default: base.
NEXT_PUBLIC_HYPERAUTH_CHAIN=base
VariableRequiredDescription
NEXT_PUBLIC_HYPERAUTH_ENCLAVE_WASM_URLNoOverride the enclave WASM URL. Defaults to the public CDN.
NEXT_PUBLIC_HYPERAUTH_VAULT_WASM_URLNoOverride the vault WASM URL. Defaults to the public CDN.
NEXT_PUBLIC_HYPERAUTH_PROJECT_IDIn productionUCAN policy token bound to your app. Without it, authenticated RPC calls return 401.
NEXT_PUBLIC_HYPERAUTH_CHAINNoDefault chain for embedded wallets. One of base, base-sepolia, monad, ethereum. Defaults to base.
The NEXT_PUBLIC_ prefix is required so the values reach the browser bundle — the provider runs on the client.

Verify the boot

A quick way to confirm the provider booted is a smoke test that fails on any SDK-related console error:
e2e/hyperauth-boot.spec.ts
import { test, expect } from "@playwright/test";

test("dashboard boots without HyperAuth errors", async ({ page }) => {
  const errors: string[] = [];
  page.on("console", (msg) => {
    if (msg.type() === "error") errors.push(msg.text());
  });
  page.on("pageerror", (err) => errors.push(err.message));

  await page.goto("/");
  await expect(page.locator("body")).toBeVisible();

  expect(errors.filter((e) => /hyperauth|wasm|worker/i.test(e))).toEqual([]);
});
Run it against a production build (next build && next start) so the middleware enforces CSP and the test exercises the same code paths as a real deployment.

Next steps