# Manual installation

Learn the steps to manually integrate Croct into your Shopify Hydrogen project.

The following guide gives you a step-by-step overview of how to install and initialize the SDK in your project.

> **Speed up your integration!**
>
> The CLI can fully automate the integration process for you. Check out the [integration guide](/reference/sdk/hydrogen/integration) to get started faster.

The SDK works with both **[React Router 7](https://reactrouter.com)** and **[Remix](https://remix.run)** Hydrogen apps. Most steps are the same. The only difference is how you wire the request context, shown for each setup in the [corresponding step](#context).

## Install the SDK \[#install]

Run the following command to install the SDK:

**Command to install the SDK**

**npm**

```sh
npm install @croct/plug-hydrogen
```

**pnpm**

```sh
pnpm add @croct/plug-hydrogen
```

**Yarn**

```sh
yarn add @croct/plug-hydrogen
```

**Bun**

```sh
bun add @croct/plug-hydrogen
```

## Set up environment variables \[#environment-variables]

> **Required permissions**
>
> When generating the [API key](/explanation/application/api-keys), check the **Issue user tokens** permission to allow the SDK to generate [signed tokens](/explanation/application/signed-tokens).

Add the following environment variables to your project replacing the placeholders with your [Application ID](https://app.croct.com/redirect/organizations/-organization-/workspaces/-workspace-/applications/-application-/integration) and [API Key](https://app.croct.com/redirect/organizations/-organization-/workspaces/-workspace-/applications/-application-/keys):

**.env**

```bash
PUBLIC_CROCT_APP_ID=<APPLICATION_ID>
CROCT_API_KEY=<API_KEY>
```

For a list of all available environment variables, see the [Environment variables](/reference/sdk/hydrogen/api/environment-variables) reference.

## Add the Vite plugin \[#vite]

Add the [Vite plugin](/reference/sdk/hydrogen/api/setup/vite-plugin) to your [Vite](https://vite.dev) config, after the Hydrogen and router plugins (`reactRouter()` for React Router 7, or `remix()` for Remix). It bakes the public Croct configuration into the browser bundle:

**JavaScript**

```diff
import {defineConfig} from 'vite';
import {hydrogen} from '@shopify/hydrogen/vite';
import {oxygen} from '@shopify/mini-oxygen/vite';
import {reactRouter} from '@react-router/dev/vite';
+import {croct} from '@croct/plug-hydrogen/vite';

export default defineConfig({
  plugins: [hydrogen(), oxygen(), reactRouter(), croct()],
});
```

**TypeScript**

```diff
import {defineConfig} from 'vite';
import {hydrogen} from '@shopify/hydrogen/vite';
import {oxygen} from '@shopify/mini-oxygen/vite';
import {reactRouter} from '@react-router/dev/vite';
+import {croct} from '@croct/plug-hydrogen/vite';

export default defineConfig({
  plugins: [hydrogen(), oxygen(), reactRouter(), croct()],
});
```

## Wire the request context \[#context]

The SDK resolves the visitor context (client ID, user token, locale, and preview state) on the server for every request. How you wire it depends on whether your app uses React Router 7 or Remix:

- **React Router 7** registers a [route middleware](/reference/sdk/hydrogen/api/setup/create-croct-middleware) on the root route.
- **Remix** builds the context in the [`getLoadContext`](https://shopify.dev/docs/api/hydrogen/latest/utilities/createrequesthandler) wrapper using the [context helper](/reference/sdk/hydrogen/api/setup/create-croct-context), since it has no route middleware.

**React Router 7 — JavaScript**

```diff
+import {createCroctMiddleware} from '@croct/plug-hydrogen/server';

+// Register the middleware so loaders and actions receive the Croct visitor context.
+export const middleware = [createCroctMiddleware()];
```

**React Router 7 — TypeScript**

```diff
+import {createCroctMiddleware} from '@croct/plug-hydrogen/server';

+// Register the middleware so loaders and actions receive the Croct visitor context.
+export const middleware = [createCroctMiddleware()];
```

**Remix — JavaScript**

```diff
import {createHydrogenContext} from '@shopify/hydrogen';
+import {createCroctContext} from '@croct/plug-hydrogen/server';

export async function createAppLoadContext(request, env, executionContext) {
  const hydrogenContext = createHydrogenContext({
    // ...your existing Hydrogen context options
  });

+  // Resolve the visitor context (Remix has no route middleware).
+  const croct = await createCroctContext(request, hydrogenContext);

  return {
    ...hydrogenContext,
+    croct,
  };
}
```

**Remix — TypeScript**

```diff
import {createHydrogenContext} from '@shopify/hydrogen';
+import {createCroctContext} from '@croct/plug-hydrogen/server';

export async function createAppLoadContext(
  request: Request,
  env: Env,
  executionContext: ExecutionContext,
) {
  const hydrogenContext = createHydrogenContext({
    // ...your existing Hydrogen context options
  });

+  // Resolve the visitor context (Remix has no route middleware).
+  const croct = await createCroctContext(request, hydrogenContext);

  return {
    ...hydrogenContext,
+    croct,
  };
}
```

## Initialize the provider \[#initialize]

Add the [provider component](/reference/sdk/hydrogen/api/components/croct-provider) to your root route, **inside** Shopify's [`<Analytics.Provider>`](https://shopify.dev/docs/api/hydrogen/latest/components/analytics/analytics-provider) so the SDK can forward storefront analytics events to Croct. You do not need to pass an [Application ID](/reference/sdk/hydrogen/api/components/croct-provider), the [Vite plugin](/reference/sdk/hydrogen/api/setup/vite-plugin) injects it from the environment:

**JavaScript**

```diff
+import {CroctProvider} from '@croct/plug-hydrogen';

export default function App() {
  const data = useRouteLoaderData('root');

  return (
    <Analytics.Provider cart={data.cart} shop={data.shop} consent={data.consent}>
+      <CroctProvider>
        <PageLayout {...data}>
          <Outlet />
        </PageLayout>
+      </CroctProvider>
    </Analytics.Provider>
  );
}
```

**TypeScript**

```diff
+import {CroctProvider} from '@croct/plug-hydrogen';

export default function App() {
  const data = useRouteLoaderData<RootLoader>('root');

  return (
    <Analytics.Provider cart={data.cart} shop={data.shop} consent={data.consent}>
+      <CroctProvider>
        <PageLayout {...data}>
          <Outlet />
        </PageLayout>
+      </CroctProvider>
    </Analytics.Provider>
  );
}
```

## Write the visitor cookies \[#cookies]

In your server entry, call the [cookie writer](/reference/sdk/hydrogen/api/setup/write-croct-cookies) to persist the visitor cookies on the response. Call it **after** committing the Hydrogen session so its `Set-Cookie` header does not overwrite Croct's cookies:

**JavaScript**

```diff
+import {writeCroctCookies} from '@croct/plug-hydrogen/server';

const response = await handleRequest(request);

if (context.session.isPending) {
  response.headers.set('Set-Cookie', await context.session.commit());
}

+// Write the visitor cookies after the session commit so it does not overwrite them.
+writeCroctCookies(response, context);

return response;
```

**TypeScript**

```diff
+import {writeCroctCookies} from '@croct/plug-hydrogen/server';

const response = await handleRequest(request);

if (context.session.isPending) {
  response.headers.set('Set-Cookie', await context.session.commit());
}

+// Write the visitor cookies after the session commit so it does not overwrite them.
+writeCroctCookies(response, context);

return response;
```

## Allow the Croct API in the CSP \[#csp]

If your storefront sets a [Content Security Policy](https://shopify.dev/docs/storefronts/headless/hydrogen/content-security-policy), allow the browser SDK to reach the Croct API by adding it to the list of permitted connection sources. The value is merged with Hydrogen's defaults, so Shopify's sources are preserved:

**JavaScript**

```diff
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
  shop: {
    checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
    storeDomain: context.env.PUBLIC_STORE_DOMAIN,
  },
+  // Allow the browser SDK to reach Croct's API (tracking, evaluation, content).
+  connectSrc: ['https://api.croct.io'],
});
```

**TypeScript**

```diff
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
  shop: {
    checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
    storeDomain: context.env.PUBLIC_STORE_DOMAIN,
  },
+  // Allow the browser SDK to reach Croct's API (tracking, evaluation, content).
+  connectSrc: ['https://api.croct.io'],
});
```

## Check your integration \[#check]

If you open your application now, it should start sending events.

To check if your integration is working, go to the [Integration page](https://app.croct.com/redirect/organizations/-organization-/workspaces/-workspace-/applications/-application-/integration) of your application.

![Integration status](/assets/reference/sdk/integration-status.png)

When working correctly, you should see a green bullet next to the **Status** label saying **"Received traffic in the past 24 hours"**. If you still do not see this message after a few minutes, see the [Troubleshooting](troubleshooting) reference.

## Explore

- [CLI](integration): Learn how to use our CLI to get started faster.
- [Troubleshooting](troubleshooting): Identify and resolve issues with your integration.
