v1.2.0

Janus Client SDK

A lightweight JavaScript library for tracking events from web applications. Automatically handles batching, retries, rate limiting, and persistence.

Lightweight (~3KB)Auto RetryBatchingPersistence

Framework Guides

Next.js — App Router#

The App Router uses React Server Components by default. Load the Janus script with next/script and track route changes with the usePathname hook. Disable trackSPANavigation to avoid duplicate pageviews from the App Router's internal replaceState calls.
app/layout.tsx
import Script from 'next/script';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://addjanus.ca/janus.js"
strategy="afterInteractive"
/>
<Script id="janus-init" strategy="afterInteractive">
{`
Janus.init('jns_your_api_key', {
trackSPANavigation: false
});
`}
</Script>
</body>
</html>
);
}
app/components/Analytics.tsx
'use client';
import { usePathname } from 'next/navigation';
import { useEffect } from 'react';
// declare global { interface Window { Janus: any } }
export function Analytics() {
const pathname = usePathname();
useEffect(() => {
// Track page view on route change
window.Janus?.page();
}, [pathname]);
return null;
}
// Add <Analytics /> to your layout.tsx inside <body>

Next.js — Pages Router#

The Pages Router works well with the default SPA tracking. Add the script in _document.tsx and initialize in _app.tsx.
pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html>
<Head>
<script
src="https://addjanus.ca/janus.js"
defer
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
pages/_app.tsx
import type { AppProps } from 'next/app';
import { useEffect } from 'react';
// declare global { interface Window { Janus: any } }
export default function App({ Component, pageProps }: AppProps) {
useEffect(() => {
window.Janus?.init('jns_your_api_key');
}, []);
return <Component {...pageProps} />;
}

Nuxt 3#

Create a client-only plugin that loads the script dynamically and tracks route changes with the Vue Router afterEach hook.
plugins/janus.client.ts
// declare global { interface Window { Janus: any } }
export default defineNuxtPlugin(() => {
const router = useRouter();
// Load the script dynamically
const script = document.createElement('script');
script.src = 'https://addjanus.ca/janus.js';
script.onload = () => {
window.Janus.init('jns_your_api_key', {
trackSPANavigation: false
});
};
document.head.appendChild(script);
// Track route changes via Vue Router
router.afterEach(() => {
window.Janus?.page();
});
});

Alternative: nuxt.config.ts#

If you prefer a simpler setup without a plugin, add the script via the Nuxt config head. Note: this uses the default SPA tracking, which works well with Nuxt's client-side navigation.
nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [
{ src: 'https://addjanus.ca/janus.js' },
{
children: "Janus.init('jns_your_api_key');",
},
],
},
},
});

Remix#

Add the script tag in root.tsx and use useLocation to track route changes. Disable trackSPANavigation for clean control over page tracking.
app/root.tsx
import { useEffect } from 'react';
import { useLocation, Scripts, Links, Outlet } from '@remix-run/react';
// declare global { interface Window { Janus: any } }
export default function App() {
const location = useLocation();
useEffect(() => {
window.Janus?.init('jns_your_api_key', {
trackSPANavigation: false
});
}, []);
// Track route changes
useEffect(() => {
window.Janus?.page();
}, [location.pathname]);
return (
<html lang="en">
<head>
<Links />
</head>
<body>
<Outlet />
<script src="https://addjanus.ca/janus.js" />
<Scripts />
</body>
</html>
);
}

Astro#

Add an inline script in your Layout component. Astro renders static HTML by default, so each page load triggers a fresh init.
src/layouts/Layout.astro
---
// Layout.astro
---
<html lang="en">
<head>
<script is:inline src="https://addjanus.ca/janus.js"></script>
<script is:inline>
Janus.init('jns_your_api_key');
</script>
</head>
<body>
<slot />
</body>
</html>

View Transitions#

If you use Astro's View Transitions, pages swap without a full reload. Listen for the astro:page-load event to track each navigation:
src/layouts/Layout.astro
<script is:inline src="https://addjanus.ca/janus.js"></script>
<script is:inline>
// Initialize once on first load
Janus.init('jns_your_api_key', {
trackSPANavigation: false
});
// Track page views on View Transitions navigation
document.addEventListener('astro:page-load', () => {
Janus.page();
});
</script>

SvelteKit#

Load the script dynamically in your root layout using onMount, and use afterNavigate to track route changes.
src/routes/+layout.svelte
<script>
import { onMount } from 'svelte';
import { afterNavigate } from '$app/navigation';
// Typing: declare global { interface Window { Janus: any } }
onMount(() => {
const script = document.createElement('script');
script.src = 'https://addjanus.ca/janus.js';
script.onload = () => {
window.Janus.init('jns_your_api_key', {
trackSPANavigation: false
});
};
document.head.appendChild(script);
});
afterNavigate(() => {
window.Janus?.page();
});
</script>
<slot />

Need Help?

If you have questions or run into issues, check out our troubleshooting guide or reach out to our support team.

Troubleshooting Guide