Install StepsKit (Next.js)

Use next/script to load StepsKit and identify the user in a single spot. This guide assumes the App Router; the Pages Router pattern is the same — just put the <Script> in _app.tsx.

Add Script to your root layout

// app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://cdn.stepskit.com/stepskit.latest.js"
          data-api-key="YOUR_API_KEY"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

strategy="afterInteractive" defers the script until after hydration — the right choice for non-critical scripts like StepsKit.

Identify the user

If your user data is available server-side (most apps), render it into data-user-* attributes and you're done — no extra JavaScript needed:

// app/layout.tsx
const user = await getCurrentUser();

<Script
  src="https://cdn.stepskit.com/stepskit.latest.js"
  data-api-key="YOUR_API_KEY"
  data-user-id={user?.id}
  data-user-email={user?.email}
  data-user-plan={user?.plan}
  strategy="afterInteractive"
/>

If the user logs in or changes plan client-side, call setUserAttributes from wherever the change happens. Use autoRefresh: true so tours re-evaluate against the new identity:

"use client";

function onPlanUpgrade(plan: string) {
  window.stepskit?.setUserAttributes(
    { plan },
    { autoRefresh: true },
  );
}

A note on the init/refresh race

If the script loads before you can identify the user, StepsKit's initial tour fetch goes out without a visitor_id. The embed coordinates internally so that when you call setUserAttributes({ id }, { autoRefresh: true }), the in-flight fetch is discarded and a fresh fetch with the correct visitor_id replaces it. This matters for "show once" tours — see Visitor identification.

The practical takeaway: identify the user as early as you can, but you don't need to delay rendering for it.