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
- Run
npm run dev - Open your app in the browser
- Open Developer Tools → Console
- Run
window.qvctrs.version— should show"2.4.0"
Checklist
- ✓Shim script with
beforeInteractivestrategy - ✓SDK script with
afterInteractivestrategy - ✓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.