Integration guides
Framework reference
Where initOtis goes for each major JavaScript framework, and which platform guide to read next.
This page is a quick reference for the init pattern in each common framework — where to put initOtis() on the server and the client, and which framework-prefixed env var to use for the browser key. Once init is wired correctly, follow the platform-level guide for the rest (flush, context propagation, AI wrapping):
- Next.js on Vercel — Next.js apps on Vercel
- Node.js server — long-running Node processes (Express, Fastify, Hono, NestJS, Node adapters of meta-frameworks)
- Serverless functions — AWS Lambda, Cloudflare Workers, Netlify, Firebase Functions, etc.
If your framework isn't listed below, the closest match is usually Express/Hono (long-running) or AWS Lambda (serverless).
Next.js
Use the dedicated guide: Next.js on Vercel. Server init goes in instrumentation.ts via register(); client init goes in app/layout.tsx via OtisProvider. Browser env var: NEXT_PUBLIC_OTIS_API_KEY.
Remix / React Router v7
Server — top-level import in entry.server.ts (generate it via npx remix reveal entry.server if it's not already present):
import "./otis"; // first import, before any handlers load
// ...rest of entry.server.tsWhere app/otis.ts calls initOtis({ apiKey: process.env.OTIS_API_KEY, serverless: true /* or false */ }). Use serverless: true on Vercel/Cloudflare adapters; omit it on the remix-serve Node adapter.
Client — top of entry.client.ts:
import { initOtis } from "@runotis/sdk";
import { hydrateRoot } from "react-dom/client";
initOtis({
apiKey: import.meta.env.VITE_OTIS_API_KEY,
serviceName: "my-app",
browser: { autoAnonymousUserId: true, autoSessionId: true },
});
hydrateRoot(/* ... */);Browser env var: VITE_OTIS_API_KEY (Remix uses Vite). Platform guide: Serverless for Vercel/Cloudflare/Netlify adapters; Node.js for remix-serve or Express adapters.
SvelteKit
Server — src/hooks.server.ts runs once per process at server start:
import { initOtis } from "@runotis/sdk";
import { OTIS_API_KEY } from "$env/static/private";
initOtis({
apiKey: OTIS_API_KEY,
serviceName: "my-app",
serverless: true, // most SvelteKit deploys are serverless (Vercel/Netlify/CF)
});
export const handle = async ({ event, resolve }) => resolve(event);Client — root +layout.svelte:
<script>
import { onMount } from "svelte";
import { initOtis } from "@runotis/sdk";
import { PUBLIC_OTIS_API_KEY } from "$env/static/public";
onMount(() => {
initOtis({
apiKey: PUBLIC_OTIS_API_KEY,
serviceName: "my-app",
browser: { autoAnonymousUserId: true, autoSessionId: true },
});
});
</script>
<slot />Browser env var: PUBLIC_OTIS_API_KEY. Platform guide: Serverless for Vercel/Netlify/Cloudflare adapters; Node.js for the Node adapter.
Nuxt
Server — server/plugins/otis.ts runs once per Nitro startup:
import { initOtis } from "@runotis/sdk";
export default defineNitroPlugin(() => {
const config = useRuntimeConfig();
initOtis({
apiKey: config.otisApiKey,
serviceName: "my-app",
serverless: true, // omit if deploying to the node-server preset
});
});Client — plugins/otis.client.ts:
import { initOtis } from "@runotis/sdk";
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig();
initOtis({
apiKey: config.public.otisApiKey,
serviceName: "my-app",
browser: { autoAnonymousUserId: true, autoSessionId: true },
});
});Register the keys in nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
otisApiKey: "", // reads OTIS_API_KEY (server-only)
public: { otisApiKey: "" }, // reads NUXT_PUBLIC_OTIS_API_KEY (browser)
},
});Platform guide: Serverless for Vercel/Netlify/Cloudflare presets; Node.js for the node-server preset.
Astro (SSR)
Static-only Astro sites only need the client init below. For SSR (output: 'server' or 'hybrid'):
Server — src/middleware.ts. Module-scope code runs once on server start:
import { initOtis } from "@runotis/sdk";
initOtis({
apiKey: import.meta.env.OTIS_API_KEY,
serviceName: "my-app",
serverless: true, // Vercel/Netlify adapters
});
export const onRequest = async (_context, next) => next();Client — script in your root layout (loaded once per page navigation in MPA mode):
---
---
<html>
<body>
<slot />
<script>
import { initOtis } from "@runotis/sdk";
initOtis({
apiKey: import.meta.env.PUBLIC_OTIS_API_KEY,
serviceName: "my-app",
browser: { autoAnonymousUserId: true, autoSessionId: true },
});
</script>
</body>
</html>Browser env var: PUBLIC_OTIS_API_KEY. Platform guide: Serverless for Vercel/Netlify adapters; Node.js for the Node adapter.
NestJS
NestJS uses dependency injection, but the singleton pattern from the Node.js guide still applies — initialize before NestFactory.create() so the singleton is ready when modules instantiate:
import { initOtis } from "@runotis/sdk";
export const otis = initOtis({
apiKey: process.env.OTIS_API_KEY!,
serviceName: "my-app",
});
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
process.on("SIGTERM", async () => {
await otis.shutdown();
process.exit(0);
});Import the otis singleton directly in services that need it, or wrap it in a provider for DI. Platform guide: Node.js.
Express, Fastify, Hono
Use the Node.js guide directly. Init via import "./otis" at the top of the server entry, before any handler files load.
For Hono on Cloudflare Workers, see the section below.
Cloudflare Workers
Use the Serverless guide. Key difference from Node: process.env is not populated at module scope. Initialize inside the fetch(req, env, ctx) handler using env.OTIS_API_KEY, and cache the instance in a module-level variable so warm requests reuse it.
Not covered here
- Bun, Deno — most code targeting these works with the Node guide. See the runtime capability table in Tracing for which features require
AsyncLocalStorage. - Google Cloud Functions, Azure Functions — same shape as AWS Lambda; follow Serverless.
- Heroku, Fly.io, Render, Railway — long-running Node servers; follow Node.js.