Choose your setup

Before installing packages, decide on your architecture and which plugins you need. This guide helps you make those choices based on your framework, rendering requirements, and feature needs.

Choose your architecture

Your architecture determines when personalization happens, in the browser, on the server, or at the edge.

ArchitectureHow it worksBest for
Client-onlySDK runs in the browser after page load. Baseline content shows briefly before variant swaps in.Simple setups where a brief flash of default content is acceptable
Hybrid SSR + clientServer or edge calls the API before rendering, then the client SDK takes over for ongoing interactions. No flash.Most production setups — best user experience with full analytics
Server-onlyServer renders personalized HTML with no client SDK.Static generation, email rendering, or environments where no client JavaScript runs

NOTE: We recommend using the hybrid approach when personalized HTML on first load matters to you. Use client-only when simplicity is your priority. Avoid server-only unless you have a specific reason as it limits analytics and experiment measurement significantly.

For hybrid and server-only implementation details, see Edge and Server Side Rendering.

Choose your packages

Start with the core packages for your framework, then add plugins based on your needs.

Core packages

For most setups, install these three packages:

$npm install @ninetailed/experience.js @ninetailed/experience.js-next @ninetailed/experience.js-utils-contentful

If you are not using Next.js (@ninetailed/experience.js-next), replace the framework package:

FrameworkFramework package
React (CRA, Vite, Remix)@ninetailed/experience.js-react
Gatsby@ninetailed/experience.js-react
Server-only (Node.js)@ninetailed/experience.js-node

NOTE: We recommend using fixed package versions instead of a range selector. For example, 7.9.0 instead od ^7.9.0. When adding or updating packages later on, make sure all packages are at the same exact version. Mixing versions causes unexpected behaviour.

Plugin packages

Add plugins based on what you need:

PluginPackageWhen to add
Insights@ninetailed/experience.js-plugin-insightsAlmost always. Required if you want experiment results, component view tracking, or conversion measurement.
SSR@ninetailed/experience.js-plugin-ssrWhen using server-side or edge personalization with a client SDK. Provides profile continuity via cookies.
Preview@ninetailed/experience.js-plugin-previewDevelopment and preview environments only. Provides a UI to test different audiences and experiences. Do not enable in production.
Privacy@ninetailed/experience.js-plugin-privacyWhen you need GDPR consent management, or when privacy requirements must be met. Blocks events and features until the visitor consents.
Google Tag Manager@ninetailed/experience.js-plugin-google-tagmanagerWhen you want personalization events forwarded to GTM.
Segment@ninetailed/experience.js-plugin-segmentWhen you want personalization events forwarded to Segment.
Contentsquare@ninetailed/experience.js-plugin-contentsquareWhen you use Contentsquare for digital experience analytics.

NOTE: The Insights plugin (@ninetailed/experience.js-plugin-insights) is different from the base analytics plugin (@ninetailed/experience.js-plugin-analytics). For built-in experiment measurement and component insights, use the Insights plugin.

Framework-specific guidance

Next.js Pages Router

  • Place NinetailedProvider in pages/_app.tsx.
  • The Pages Router integration automatically tracks page changes.

NOTE: Do not add manual page() calls for route navigation.

  • Use getStaticProps with revalidate (ISR) for the best balance of performance and fresh content.
  • For SSR/edge, add middleware and the SSR plugin.

Example

// in pages/_app.tsx
import { NinetailedProvider } from "@ninetailed/experience.js-next";
import { InsightsPlugin } from "@ninetailed/experience.js-plugin-insights";
export default function App({ Component, pageProps }) {
return (
<NinetailedProvider
clientId="<your-client-id>"
environment="<your-environment>"
plugins={[
new InsightsPlugin(),
// Add other plugins as needed.
]}
>
<Component {...pageProps} />
</NinetailedProvider>
);
}

Next.js App Router

  • The provider must be in a Client Component. Create a providers.tsx wrapper with 'use client' and place it in your root layout.
  • The current SDKs do not auto-track App Router navigation. Add a dedicated page tracker component.
  • Server Components cannot use personalization hooks. Fetch content in a Server Component and pass it to a Client Component that handles personalization.

Examples

// in app/providers.tsx
"use client";
import { NinetailedProvider } from "@ninetailed/experience.js-next";
import { InsightsPlugin } from "@ninetailed/experience.js-plugin-insights";
export function Providers({ children }) {
return (
<NinetailedProvider
clientId="<your-client-id>"
environment="<your-environment>"
plugins={[
new InsightsPlugin(),
// Add other plugins as needed.
]}
>
{children}
</NinetailedProvider>
);
}
// in app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
'use client';
import { useEffect } from 'react';
import { usePathname } from 'next/navigation';
import { useNinetailed } from '@ninetailed/experience.js-react';
export default function TrackPage() {
const pathname = usePathname();
const { page } = useNinetailed();
useEffect(() => {
void page();
[page, pathname];
});
return null;
}

Gatsby

  • Place the provider in gatsby-browser.js (and gatsby-ssr.js). The provider needs to be declared both in the browser and the SSR file to avoid React hydration issues, such as, the server-generated React tree not matching the client-generated one.
  • Personalization is client-side only — static HTML shows the baseline, and the SDK swaps variants after hydration
  • Use the GATSBY_ prefix for environment variables (e.g., GATSBY_NINETAILED_CLIENT_ID)

Example

// in gatsby-browser.js
import { NinetailedProvider } from "@ninetailed/experience.js-react";
import { InsightsPlugin } from "@ninetailed/experience.js-plugin-insights";
export const wrapRootElement = ({ element }) => (
<NinetailedProvider
clientId="<your-client-id>"
environment="<your-environment>"
plugins={[
new InsightsPlugin(),
// Add other plugins as needed.
]}
>
{element}
</NinetailedProvider>
);
// in gatsby-ssr.js
export const wrapRootElement = ({ element }) => {
// Reuse or redeclare the same implementation
// as in gatsby-browser.js.
};

Plain React (Vite)

  • Place the provider at your app root
  • Personalization is client-side only
  • No SSR capabilities. Consider Next.js if you need server-side personalization.

Example

import { NinetailedProvider } from "@ninetailed/experience.js-react";
import { InsightsPlugin } from "@ninetailed/experience.js-plugin-insights";
export function App() {
return (
<NinetailedProvider
clientId="<your-client-id>"
environment="<your-environment>"
plugins={[
new InsightsPlugin(),
// Add other plugins as needed.
]}
>
<YourApplication />
</NinetailedProvider>
);
}

Environment variables

At minimum, for Next.js, you need:

VariablePurpose
NEXT_PUBLIC_NINETAILED_CLIENT_IDYour Contentful Personalization API key.
NEXT_PUBLIC_NINETAILED_ENVIRONMENTThe environment slug (usually main).

For edge or server-side setups, you may also need NINETAILED_API_KEY and NINETAILED_ENVIRONMENT without the NEXT_PUBLIC_ prefix.

Add real values to .env.local and placeholders to .env.example. Never commit API keys or tokens to source control.

Next steps