Next.js Integration

Use the command queue pattern with next/script for optimal loading in Next.js App Router or Pages Router.

Easy2 minutes

App Router (Recommended)

Edit your root layout

Open app/layout.tsx and add the scripts inside the <body> tag:

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

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {/* qvctrs SDK - Shim (runs immediately) */}
        <Script
          id="qvctrs-shim"
          strategy="beforeInteractive"
          dangerouslySetInnerHTML={{
            __html: `
              window.qvctrs=window.qvctrs||{};window.qvctrs.q=window.qvctrs.q||[];
              window.qvctrs.init=window.qvctrs.init||function(){window.qvctrs.q.push(['init'].concat(Array.prototype.slice.call(arguments)))};
              window.qvctrs.init({siteId:'YOUR_SITE_ID'});
            `,
          }}
        />
        {/* qvctrs SDK - Main script (loads async) */}
        <Script
          src="https://www.qvctrs.com/qvctrs-sdk-v2.js"
          strategy="afterInteractive"
        />

        {children}
      </body>
    </html>
  );
}

Script Strategies

beforeInteractive ensures the shim runs first. afterInteractive loads the SDK without blocking hydration.

Pages Router

Edit _app.tsx or _document.tsx

For Pages Router, add the scripts to pages/_app.tsx:

// pages/_app.tsx
import type { AppProps } from 'next/app';
import Script from 'next/script';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      {/* qvctrs SDK */}
      <Script
        id="qvctrs-shim"
        strategy="beforeInteractive"
        dangerouslySetInnerHTML={{
          __html: `
            window.qvctrs=window.qvctrs||{};window.qvctrs.q=window.qvctrs.q||[];
            window.qvctrs.init=window.qvctrs.init||function(){window.qvctrs.q.push(['init'].concat(Array.prototype.slice.call(arguments)))};
            window.qvctrs.init({siteId:'YOUR_SITE_ID'});
          `,
        }}
      />
      <Script
        src="https://www.qvctrs.com/qvctrs-sdk-v2.js"
        strategy="afterInteractive"
      />

      <Component {...pageProps} />
    </>
  );
}

Alternative: Using _document.tsx

If you need even earlier loading, add to pages/_document.tsx:

// pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <script
          dangerouslySetInnerHTML={{
            __html: `
              window.qvctrs=window.qvctrs||{};window.qvctrs.q=window.qvctrs.q||[];
              window.qvctrs.init=window.qvctrs.init||function(){window.qvctrs.q.push(['init'].concat(Array.prototype.slice.call(arguments)))};
              window.qvctrs.init({siteId:'YOUR_SITE_ID'});
            `,
          }}
        />
        <script src="https://www.qvctrs.com/qvctrs-sdk-v2.js" async />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

Environment Variables (Optional)

Store your Site ID in an environment variable:

# .env.local
NEXT_PUBLIC_QVCTRS_SITE_ID=qv_your_site_id_here

Then reference it in your layout:

window.qvctrs.init({siteId:'undefined'});

Verify Installation

  1. Run npm run dev
  2. Open your app in the browser
  3. Open Developer Tools → Console
  4. Run window.qvctrs.version — should show "2.4.0"

Checklist

  • Shim script with beforeInteractive strategy
  • SDK script with afterInteractive strategy
  • Site ID replaced with your actual ID
  • Verified in browser console

Common Issues

Hydration mismatch warnings

If you see hydration warnings, ensure you're using dangerouslySetInnerHTML with backtick template strings, not JSX children.

SDK not initializing on navigation

The SDK initializes once per session and persists across client-side navigation automatically. You don't need to re-initialize on route changes.