Skip to content
KismetKismetDevelopers
llms.txt

Install on Next.js

View .md

@kismet-tech/telemetry-next wraps the core for the App Router: a middleware that does the whole contract on every page request, a seed component for the root layout, and the checkout call. Edge or Node runtime; Next 14.2 or later.

Contract 1.0 limit: externalListingId can be emitted, but ingest-side resolution and generic conversions are Contract 1.1 additions. For property-level attribution today, arrange a registered serving-URL mapping or use a known Kismet property slug. Confirm backend support before relying on the 1.1 fields.

Request from Kismet during onboarding: the collection slug and the collection’s tracking key. Put them in the environment, never in a page:

KISMET_COLLECTION_SLUG=your-collection
KISMET_TRACKING_KEY=ctk_...

Confirm your production and staging hostnames are on the collection’s authorized domains. The browser tracker’s origin is its credential; an unregistered hostname means client-side events are rejected once enforcement is on.

Terminal window
npm install @kismet-tech/[email protected] @kismet-tech/[email protected]
middleware.ts
import { createKismetMiddleware, KISMET_MATCHER, consentFromCookie } from '@kismet-tech/telemetry-next';
export const middleware = createKismetMiddleware({
collectionSlug: process.env.KISMET_COLLECTION_SLUG!,
trackingKey: process.env.KISMET_TRACKING_KEY!,
// Your consent manager's cookie. Consent gates the session, not the recording.
consent: consentFromCookie('CookieConsent', /statistics:true/),
// Which URLs are what, once. Your own listing id is fine: send it as
// externalListingId; ingest resolution requires Contract 1.1.
profile: {
property: { pattern: /^\/stays\/[^/]+\/([^/]+)\/?$/, as: 'externalListingId' },
searchPaths: ['/stays', /^\/stays\/in\//],
intent: { path: '/stays/checkout', checkinParam: 'checkin', checkoutParam: 'checkout', guestsParam: 'guests' },
},
});
export const config = { matcher: KISMET_MATCHER };

KISMET_MATCHER covers pages and the two agent index files (/llms.txt, /.well-known/llm-index.json) and never assets or /api/*.

Already have a middleware? Pass it as next. Telemetry resolves the visitor first, calls yours with the seed headers on the request, and adds its cookies and headers to whatever you return:

export const middleware = createKismetMiddleware({
...config,
next: async (request, event, visitor) => {
// visitor.kidSid, visitor.tier and visitor.classification are available here
return NextResponse.next({ request: { headers: request.headers } });
},
});

In the root layout, first thing in <head>:

app/layout.tsx
import { KismetSeed } from '@kismet-tech/telemetry-next/seed';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
<KismetSeed collectionSlug={process.env.KISMET_COLLECTION_SLUG!} />
</head>
<body>{children}</body>
</html>
);
}

KismetSeed reads the headers the middleware attached, which makes the layout dynamic. That is what you want: a seeded page must never be statically cached. It renders one inline script that sets the seed and then appends the browser tracker tag itself, because React 19 hoists <script async src> elements ahead of other head content and a separate tag would load before the seed.

After Kismet enables your collection, add visitorRecognition: true to the middleware configuration above and retain the explicit consent hook. KismetSeed automatically makes the same-origin follow-up that receives _kid_vid. No additional application API route is required.

The default matcher includes /__kismet/visitor; include it in custom matchers and exclude it from shared caches. See the returning-visitor guide for base paths, Pages Router and cookie recovery checks.

From the server-side handler that knows the reservation succeeded, once:

import { bookingBridge } from '@kismet-tech/telemetry-next';
await bookingBridge(config, { kidSid, confirmationCode, bookingEngine: 'custom', domain: 'example.co.uk' });

kidSid is the _kid_sid cookie on the checkout request; carry it through your booking pipeline to wherever the confirmation code is known. The call is bounded and never throws; it must not affect the booking.

The after() example below requires Next.js 15.1 or later. On earlier supported versions, use the runtime’s supported background scheduler or the existing middleware path.

When the listing id is only known at render (the URL has no id the middleware can read), record the property view from the page’s server component:

import { headers } from 'next/headers';
import { after } from 'next/server';
import { trackServerPropertyView } from '@kismet-tech/telemetry-next';
after(trackServerPropertyView(config, {
headers: await headers(), url: currentUrl,
externalListingId: listing.id, checkIn, checkOut, guests,
}));

Saves, dates picked in a picker, the book button. The client helpers dispatch the events the browser tracker bridges; never call window.Kismet.track() directly.

import { createTracker } from '@kismet-tech/telemetry-next/client';
const track = createTracker({ collectionSlug: 'your-collection' });
track.propertyView({ externalListingId: listing.id, checkIn, checkOut, guests, stayTotalCents });
track.save({ externalListingId: listing.id });
track.bookIntent({ externalListingId: listing.id, checkIn, checkOut, guests, stayTotalCents });

What the middleware does on every page request

Section titled “What the middleware does on every page request”

Resolves the visitor (threaded id, cookie, suppressed for bots and visitors without consent, else a locally minted id reconciled with Kismet after the response), emits one server-plane event (agent surfaces as fetch with no identity), sets _kid_sid on the dotted serving domain; optional visitor recognition persists _kid_vid through a bounded follow-up request, marks the response private, no-store, and passes the seed to the layout through request headers. The page never waits on Kismet, and a middleware error serves the page without telemetry rather than failing it. The overview walks through the six steps; the contract specifies them.

  1. Load a page in a fresh browser. Expect a _kid_sid cookie on the dotted domain and window.Kismet._kidSid equal to it, with the response header x-kismet-anchor-tier: minted.
  2. Reload with consent retained. The same session is adopted; an enabled visitor follow-up may set or refresh _kid_vid.
  3. curl -A GPTBot https://your-host/some-page: no Set-Cookie, and the page seeds _sidSuppressed.
  4. curl https://your-host/llms.txt: recorded as a fetch with no session.
  5. From a consent jurisdiction without consent: no cookie, page still recorded.
  6. If using the booking bridge, complete a staging test booking and verify its successful response and session join.
  7. With visitor recognition enabled, restart the browser, remove only _kid_sid, and visit again. Expect the same _kid_vid with a new session; Kismet can verify the stored visitor link. Withdraw consent and confirm both cookies are removed on the next adapter request.

The package’s own tests run it through the contract’s conformance suite with a real NextRequest. To run the suite against your own middleware in CI:

import { runConformance, formatReport } from '@kismet-tech/telemetry/conformance';
const report = await runConformance((env) => makeYourDriver(env));
if (!report.ok) throw new Error(formatReport(report));

The adapter is about two hundred lines over the core. If you would rather own them, src/conformance/reference-adapter.js inside @kismet-tech/telemetry is the same logic as a plain Request to Response handler, and the contract is the specification it implements.

To review browser code before it changes on your site, configure the pinned browser tracker. Pinning the npm package or WordPress plugin alone does not pin the default k.js URL. The pinned release works with version 1.1.0 and preserves your existing consent and returning-visitor configuration.

Follow the consent integration guide to map your saved banner choice to both server adapters and browser tracker 1.2.0. Kismet can prepare the configuration if you share your CMP, consent-cookie format and saved-choice callback.