SSR Support#

@pulsora/react is SSR-safe—initialization only happens in the browser. Follow these patterns to avoid hydration warnings and access tokens securely.

Next.js (App Router)#

// app/providers.tsx
'use client';

import { PulsoraProvider, usePageview } from '@pulsora/react';
import { usePathname, useSearchParams } from 'next/navigation';

function PageviewTracker() {
  const pathname = usePathname();
  const search = useSearchParams();
  usePageview({ trigger: `${pathname}?${search}` });
  return null;
}

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <PulsoraProvider
      config={{
        apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN!,
      }}
    >
      <PageviewTracker />
      {children}
    </PulsoraProvider>
  );
}

Then import Providers in app/layout.tsx. Because the provider lives inside a "use client" module, no tracking code runs during server render.

Next.js (Pages Router)#

// pages/_app.tsx
import type { AppProps } from 'next/app';
import { PulsoraProvider, usePageview } from '@pulsora/react';
import { useRouter } from 'next/router';

function AnalyticsBridge() {
  const router = useRouter();
  usePageview({ trigger: router.asPath });
  return null;
}

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <PulsoraProvider
      config={{ apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN! }}
    >
      <AnalyticsBridge />
      <Component {...pageProps} />
    </PulsoraProvider>
  );
}

Remix#

// app/root.tsx
import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useLocation,
} from '@remix-run/react';
import { PulsoraProvider, usePageview } from '@pulsora/react';

function Analytics() {
  const location = useLocation();
  usePageview({ trigger: location.pathname + location.search });
  return null;
}

export default function App() {
  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <PulsoraProvider config={{ apiToken: process.env.PULSORA_PUBLIC_KEY! }}>
          <Analytics />
          <Outlet />
        </PulsoraProvider>
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

Remix renders the tree on the server first, but the provider only initializes once the client hydrates. Always reference your API token through loader data or environment variables prefixed with PUBLIC_ (exposed at build time).

Gatsby#

// gatsby-browser.js
import React from 'react';
import { PulsoraProvider } from '@pulsora/react';

export const wrapRootElement = ({ element }) => (
  <PulsoraProvider config={{ apiToken: process.env.GATSBY_PULSORA_TOKEN! }}>
    {element}
  </PulsoraProvider>
);

Gatsby runs gatsby-browser.js only in the browser, so initialization is safe.

Common Gotchas#

  • Hydration mismatch — Ensure all Pulsora code lives in client components ('use client') or browser-only entry points.
  • Environment variables — Expose public tokens using the framework’s conventions (NEXT_PUBLIC_, GATSBY_, VITE_, etc.).
  • Server rendering usage — Never call hooks from server components. Wrap them in client modules or use dynamic imports with { ssr: false }.

Need more implementation patterns? Check the React examples →