Next.js
App Router, Pages Router, and SSR notes.
App Router
The provider is a client component. Put it in its own file so your layout can stay a server component.
app/providers.tsx
"use client";
import type { ReactNode } from "react";
import { CairnProvider } from "@cairnkit/react";
import { useAppRouterAdapter } from "@cairnkit/next";
import { CairnOverlay } from "@cairnkit/ui";
import { flows } from "@/walkthrough/flows";
export function Providers({ children }: { children: ReactNode }) {
return (
<CairnProvider flows={flows} router={useAppRouterAdapter()}>
{children}
<CairnOverlay />
</CairnProvider>
);
}app/layout.tsx
import { Providers } from "./providers";
import "@cairnkit/ui/styles.css";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body><Providers>{children}</Providers></body>
</html>
);
}Pages Router
Same shape, different adapter. Wrap in _app.
pages/_app.tsx
import type { AppProps } from "next/app";
import { CairnProvider } from "@cairnkit/react";
import { usePagesRouterAdapter } from "@cairnkit/next";
import { CairnOverlay } from "@cairnkit/ui";
import { flows } from "../walkthrough/flows";
import "@cairnkit/ui/styles.css";
export default function App({ Component, pageProps }: AppProps) {
return (
<CairnProvider flows={flows} router={usePagesRouterAdapter()}>
<Component {...pageProps} />
<CairnOverlay />
</CairnProvider>
);
}Server rendering
The overlay renders nothing on the server and mounts through a portal after hydration, so there is no markup mismatch. resolveAnchor returns null when there is no document rather than throwing, so importing Cairn from a server component is safe.
Deep links
useTourDeepLink() starts a flow from ?tour=<flowId>, so support can send someone straight into a guide.
"use client";
import { useTourDeepLink } from "@cairnkit/react";
export function DeepLink() {
useTourDeepLink(); // or useTourDeepLink("guide") for ?guide=
return null;
}Known gotchas
- TypeScript 7. Next 15 rejects the native TS7 compiler. Pin
typescript@^5.7until Next 16. - Turbopack and workspace packages. Developing Cairn alongside your app needs
transpilePackagesinnext.config.ts. Not needed when installing from npm. - Route changes are asynchronous. Anchors are waited for, so a step whose action navigates just works — see going off-script.