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
- Conversion tracking — patrón completo click → conversion.
- Custom cart bridge — sincronizar el estado del carrito con el widget.
- Variables de entorno — tabla central por stack.