Installation
npm install --save @outlit/browser
Quick Start
Add Outlit to your base layout:
---
// src/layouts/BaseLayout.astro
const PUBLIC_OUTLIT_KEY = import.meta.env.PUBLIC_OUTLIT_KEY
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>My Site</title>
</head>
<body>
<slot />
<script>
import outlit from '@outlit/browser'
outlit.init({
publicKey: import.meta.env.PUBLIC_OUTLIT_KEY,
trackPageviews: true,
})
</script>
</body>
</html>
Add your public key to .env:PUBLIC_OUTLIT_KEY=pk_your_public_key_here
Alternative: Inline Script
Use an inline script for better performance:
---
// src/layouts/BaseLayout.astro
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>My Site</title>
</head>
<body>
<slot />
<script>
import outlit from '@outlit/browser'
outlit.init({
publicKey: import.meta.env.PUBLIC_OUTLIT_KEY,
trackPageviews: true,
})
</script>
</body>
</html>
Tracking Events
Track events in Astro components:
---
// src/components/PricingButton.astro
---
<button id="pricing-btn" data-plan="pro">
View Pricing
</button>
<script>
import outlit from '@outlit/browser'
const button = document.getElementById('pricing-btn')
button?.addEventListener('click', () => {
const plan = button.getAttribute('data-plan')
outlit.track('pricing_clicked', { plan })
})
</script>
With Client Directives
Use client directives for interactive components:
---
// src/pages/index.astro
import PricingButton from '../components/PricingButton.jsx'
---
<html>
<body>
<PricingButton client:load />
</body>
</html>
Framework Islands
React Island
// src/components/PricingButton.jsx
import { useEffect } from 'react'
import outlit from '@outlit/browser'
export default function PricingButton() {
function handleClick() {
outlit.track('pricing_clicked', { plan: 'pro' })
}
return (
<button onClick={handleClick}>
View Pricing
</button>
)
}
Vue Island
<!-- src/components/PricingButton.vue -->
<script setup>
import outlit from '@outlit/browser'
function handleClick() {
outlit.track('pricing_clicked', { plan: 'pro' })
}
</script>
<template>
<button @click="handleClick">
View Pricing
</button>
</template>
Svelte Island
<!-- src/components/PricingButton.svelte -->
<script>
import outlit from '@outlit/browser'
function handleClick() {
outlit.track('pricing_clicked', { plan: 'pro' })
}
</script>
<button on:click={handleClick}>
View Pricing
</button>
Identifying Users
Identify users after authentication:
---
// src/components/LoginForm.astro
---
<form id="login-form">
<input type="email" name="email" placeholder="Email" />
<input type="password" name="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
<script>
import outlit from '@outlit/browser'
const form = document.getElementById('login-form')
form?.addEventListener('submit', async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const email = formData.get('email')
const password = formData.get('password')
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
})
const user = await response.json()
outlit.identify({
email: user.email,
userId: user.id,
traits: {
name: user.name,
plan: user.plan
}
})
window.location.href = '/dashboard'
})
</script>
Auth State Management
For apps with authentication, use setUser() and clearUser() to sync user identity:
---
// src/components/AuthObserver.astro
---
<script>
import outlit from '@outlit/browser'
// Check for existing auth session on page load
async function checkAuth() {
const response = await fetch('/api/auth/session')
const session = await response.json()
if (session?.user) {
outlit.setUser({
email: session.user.email,
userId: session.user.id,
traits: {
name: session.user.name,
plan: session.user.plan
}
})
}
}
checkAuth()
// Listen for auth state changes (e.g., from Supabase, Firebase)
window.addEventListener('auth-state-changed', (event) => {
const user = event.detail?.user
if (user) {
outlit.setUser({
email: user.email,
userId: user.id
})
} else {
outlit.clearUser()
}
})
</script>
Using setUser() ensures the user identity persists across page navigations and is properly set when users return to your site already logged in.
View Transitions
Track navigation with Astro’s View Transitions:
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions'
---
<!DOCTYPE html>
<html lang="en">
<head>
<ViewTransitions />
</head>
<body>
<slot />
<script>
import outlit from '@outlit/browser'
outlit.init({
publicKey: import.meta.env.PUBLIC_OUTLIT_KEY,
trackPageviews: false, // Manual tracking
})
// Track pageviews (fires on initial load and navigation with View Transitions)
document.addEventListener('astro:page-load', () => {
outlit.track('pageview', {
path: window.location.pathname
})
})
</script>
</body>
</html>
API Routes
Track events from API endpoints:
// src/pages/api/checkout.ts
import type { APIRoute } from 'astro'
import { Outlit } from '@outlit/node'
const outlit = new Outlit({
privateKey: import.meta.env.OUTLIT_PRIVATE_KEY
})
export const POST: APIRoute = async ({ request }) => {
const body = await request.json()
await outlit.track({
visitorId: body.userId,
event: 'checkout_completed',
properties: {
amount: body.amount,
plan: body.plan
}
})
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' }
})
}
SSR Pages
For SSR pages, ensure tracking runs client-side:
---
// src/pages/products/[id].astro
export const prerender = false // SSR mode
const { id } = Astro.params
const product = await fetchProduct(id)
---
<html>
<body>
<h1>{product.name}</h1>
<div id="product-data" data-product-id={id} style="display: none;"></div>
<script>
import outlit from '@outlit/browser'
// Track product view
const productId = document.getElementById('product-data')?.dataset.productId
outlit.track('product_viewed', {
productId
})
</script>
</body>
</html>
Content Collections
Track interactions with content:
---
// src/pages/blog/[...slug].astro
import { getEntry } from 'astro:content'
const entry = await getEntry('blog', Astro.params.slug)
---
<article>
<h1>{entry.data.title}</h1>
<div set:html={entry.body} />
<div id="blog-data" data-slug={Astro.params.slug} data-title={entry.data.title} style="display: none;"></div>
</article>
<script>
import outlit from '@outlit/browser'
const blogData = document.getElementById('blog-data')
outlit.track('blog_post_viewed', {
slug: blogData?.dataset.slug,
title: blogData?.dataset.title
})
</script>
Track contact lifecycle events:
---
// src/components/OnboardingComplete.astro
---
<button id="complete-onboarding">Complete Setup</button>
<script>
import outlit from '@outlit/browser'
document.getElementById('complete-onboarding')?.addEventListener('click', () => {
outlit.user.activate({ flow: 'onboarding', step: 'completed' })
})
</script>
Stage methods (user.activate, user.engaged) require the user to be identified first using setUser() or identify().
Account billing (paid, churned, trialing) is tracked separately. If you’ve connected Stripe, billing is automatic. For manual billing, see Stages & Billing.
Consent Management
Handle user consent:
---
// src/components/CookieBanner.astro
---
<div id="cookie-banner" style="display: none;">
<p>We use cookies to improve your experience.</p>
<button id="accept-btn">Accept</button>
</div>
<script>
import outlit from '@outlit/browser'
// Initialize without auto-tracking
outlit.init({
publicKey: import.meta.env.PUBLIC_OUTLIT_KEY,
autoTrack: false
})
// Check consent
const hasConsent = localStorage.getItem('tracking-consent')
if (hasConsent === 'true') {
outlit.enableTracking()
} else {
document.getElementById('cookie-banner').style.display = 'block'
}
// Handle accept
document.getElementById('accept-btn')?.addEventListener('click', () => {
localStorage.setItem('tracking-consent', 'true')
outlit.enableTracking()
document.getElementById('cookie-banner').style.display = 'none'
})
</script>
<style>
#cookie-banner {
position: fixed;
bottom: 1rem;
left: 1rem;
right: 1rem;
padding: 1rem;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 0.5rem;
}
</style>
Environment Variables
Configure environment variables:
# .env
# Public key (used in browser)
PUBLIC_OUTLIT_KEY=pk_your_public_key_here
# Private key (used in API routes)
OUTLIT_PRIVATE_KEY=sk_your_private_key_here
Never expose your private key in client-side code. Only use it in API routes.
TypeScript Support
Full TypeScript support is included:
import outlit, { type OutlitOptions } from '@outlit/browser'
const options: OutlitOptions = {
publicKey: import.meta.env.PUBLIC_OUTLIT_KEY,
trackPageviews: true,
flushInterval: 5000
}
outlit.init(options)
Next Steps