Saltar al contenido principal

Next.js / React

Esta receta integra Neuroon en una app Next.js 14+ (App Router). El patrón es el mismo para cualquier framework React server-rendered.

Estructura final

app/
├── api/
│ └── neuroon-token/
│ └── route.ts # Emite el Widget Token
├── layout.tsx # Embebe el widget
└── page.tsx
.env.local # NEUROON_API_KEY, NEUROON_SHOP_ID

1. Variables de entorno

.env.local:

NEUROON_API_KEY=sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NEUROON_SHOP_ID=shop_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
NEUROON_API_URL=https://api.neuroon.ai

Server-only (sin prefijo NEXT_PUBLIC_). Si no tienes la API Key, obtenla aquí.

2. Route Handler que firma el Widget Token

Tu servidor firma el token localmente con la Shop API Key como secreto HMAC. No llamas a Neuroon para emitirlo.

app/api/neuroon-token/route.ts:

import { NextResponse } from 'next/server';
import { createHmac } from 'node:crypto';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

let cached: { token: string; expiresAt: number } | null = null;

function signToken(): { token: string; expiresAt: number } {
const ts = Math.floor(Date.now() / 1000);
const payload = `${process.env.NEUROON_SHOP_ID}:${ts}`;
const sig = createHmac('sha256', process.env.NEUROON_API_KEY!).update(payload).digest('hex');
const token = Buffer.from(`${payload}:${sig}`, 'utf8').toString('base64url');
return { token, expiresAt: (ts + 24 * 3600) * 1000 };
}

export async function GET() {
const now = Date.now();
if (!cached || now >= cached.expiresAt - 5 * 60_000) {
cached = signToken();
}
return NextResponse.json({ token: cached.token });
}

Patrón completo (Node, .NET, Python, PHP) con cache Redis distribuida en Recipe · Server-to-server token.

3. Embebido del widget en el layout

app/layout.tsx:

import Script from 'next/script';
import { createHmac } from 'node:crypto';

let tokenCache: { token: string; expiresAt: number } | null = null;

function getWidgetToken(): string {
const now = Date.now();
if (tokenCache && now < tokenCache.expiresAt - 5 * 60_000) return tokenCache.token;
const ts = Math.floor(now / 1000);
const payload = `${process.env.NEUROON_SHOP_ID}:${ts}`;
const sig = createHmac('sha256', process.env.NEUROON_API_KEY!).update(payload).digest('hex');
const token = Buffer.from(`${payload}:${sig}`, 'utf8').toString('base64url');
tokenCache = { token, expiresAt: (ts + 24 * 3600) * 1000 };
return token;
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
const token = getWidgetToken();

return (
<html lang="es">
<body>
{children}
<div id="neuroon-search" />
<Script
src="https://cdn.neuroon.ai/widget@0.9.10/widget.js"
data-token={token}
data-container="#neuroon-search"
data-theme="auto"
data-locale="es"
strategy="afterInteractive"
/>
</body>
</html>
);
}

El Script con strategy="afterInteractive" se inyecta tras la hidratación inicial de Next, no bloquea el First Paint. La cache tokenCache por instancia evita firmar más de una vez cada ~24 h. En multi-pod (Vercel) usa Redis/KV.

4. Sincroniza productos desde un Server Action o cron

app/actions/sync-products.ts:

'use server';

export async function syncProducts(products: Array<{
externalId: string;
name: string;
price: number;
currency: string;
url: string;
}>) {
const res = await fetch(
`${process.env.NEUROON_API_URL}/api/plugin/shops/${process.env.NEUROON_SHOP_ID}/products/sync`,
{
method: 'POST',
headers: {
'X-Shop-API-Key': process.env.NEUROON_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({ syncType: 'INCREMENTAL', products }),
},
);

if (!res.ok) {
throw new Error(`Sync failed: ${res.status}`);
}

return (await res.json()) as {
totalReceived: number;
newProducts: number;
updatedProducts: number;
failed: number;
};
}

Llámalo desde donde tengas tu catálogo (cron job con inngest/vercel-cron, webhook de tu CMS, Server Action manual). El backend acepta hasta 500 productos por batch.

5. Tracking de conversiones server-side

Dentro del Server Action que confirma el pedido:

'use server';

export async function confirmOrder(order: {
id: string;
total: number;
currency: string;
items: Array<{ sku: string; quantity: number; lineTotal: number }>;
}) {
await fetch(
`${process.env.NEUROON_API_URL}/api/plugin/shops/${process.env.NEUROON_SHOP_ID}/track/conversion`,
{
method: 'POST',
headers: {
'X-Shop-API-Key': process.env.NEUROON_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({
orderId: order.id,
orderValue: order.total,
currency: order.currency,
conversions: order.items.map((i) => ({
productId: i.sku,
quantity: i.quantity,
lineTotal: i.lineTotal,
})),
}),
},
);
}

Conversiones server-side son resistentes a adblockers (a diferencia del tracking del navegador).

Comprobación rápida

# 1. Token endpoint funciona
curl http://localhost:3000/api/neuroon-token

# 2. Carga la home y haz click en el widget
open http://localhost:3000

Si el widget no aparece, revisa Troubleshooting.

Producción

Solo cambias dos variables al pasar a producción:

- NEUROON_API_URL=https://dev-api.neuroon.ai
+ NEUROON_API_URL=https://api.neuroon.ai

- NEUROON_API_KEY=sk_dev_xxxxxxxxxxxxxxxxxxxxxxxx
+ NEUROON_API_KEY=sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

El CDN del widget (cdn.neuroon.ai/widget@<version>/widget.js) es el mismo en ambos entornos.

Siguientes pasos